1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31
32#include <private/qqmldata_p.h>
33#include <qjsengine.h>
34#include <qjsvalueiterator.h>
35#include <qgraphicsitem.h>
36#include <qstandarditemmodel.h>
37#include <QtCore/qnumeric.h>
38#include <qqmlengine.h>
39#include <qqmlcomponent.h>
40#include <stdlib.h>
41#include <private/qv4alloca_p.h>
42#include <private/qjsvalue_p.h>
43#include <QScopeGuard>
44
45#ifdef Q_CC_MSVC
46#define NO_INLINE __declspec(noinline)
47#else
48#define NO_INLINE __attribute__((noinline))
49#endif
50
51Q_DECLARE_METATYPE(QList<int>)
52Q_DECLARE_METATYPE(QObjectList)
53
54class tst_QJSEngine : public QObject
55{
56 Q_OBJECT
57
58public:
59 tst_QJSEngine();
60 virtual ~tst_QJSEngine();
61
62private slots:
63 void callQObjectSlot();
64 void constructWithParent();
65 void newObject();
66 void newArray();
67 void newArray_HooliganTask218092();
68 void newArray_HooliganTask233836();
69 void toScriptValue_data();
70 void toScriptValue();
71 void toScriptValuenotroundtripped_data();
72 void toScriptValuenotroundtripped();
73 void newVariant();
74 void newVariant_valueOfToString();
75 void newVariant_valueOfEnum();
76 void newRegExp();
77 void jsRegExp();
78 void newDate();
79 void jsParseDate();
80 void newQObject();
81 void newQObjectRace();
82 void newQObject_ownership();
83 void newQObject_deletedEngine();
84 void newQObjectPropertyCache();
85 void newQMetaObject();
86 void exceptionInSlot();
87 void globalObjectProperties();
88 void globalObjectEquals();
89 void globalObjectProperties_enumerate();
90 void createGlobalObjectProperty();
91 void globalObjectWithCustomPrototype();
92 void builtinFunctionNames_data();
93 void builtinFunctionNames();
94 void evaluate_data();
95 void evaluate();
96 void errorMessage_QT679();
97 void valueConversion_basic();
98 void valueConversion_QVariant();
99 void valueConversion_basic2();
100 void valueConversion_dateTime();
101 void valueConversion_regExp();
102 void valueConversion_RegularExpression();
103 void castWithMultipleInheritance();
104 void collectGarbage();
105 void collectGarbageNestedWrappersTwoEngines();
106 void gcWithNestedDataStructure();
107 void stacktrace();
108 void numberParsing_data();
109 void numberParsing();
110 void automaticSemicolonInsertion();
111 void errorConstructors();
112 void argumentsProperty_globalContext();
113 void argumentsProperty_JS();
114 void jsNumberClass();
115 void jsForInStatement_simple();
116 void jsForInStatement_prototypeProperties();
117 void jsForInStatement_mutateWhileIterating();
118 void jsForInStatement_arrays();
119 void jsForInStatement_constant();
120 void with_constant();
121 void stringObjects();
122 void jsStringPrototypeReplaceBugs();
123 void getterSetterThisObject_global();
124 void getterSetterThisObject_plain();
125 void getterSetterThisObject_prototypeChain();
126 void jsContinueInSwitch();
127 void jsShadowReadOnlyPrototypeProperty();
128 void jsReservedWords_data();
129 void jsReservedWords();
130 void jsFutureReservedWords_data();
131 void jsFutureReservedWords();
132 void jsThrowInsideWithStatement();
133 void reentrancy_globalObjectProperties();
134 void reentrancy_Array();
135 void reentrancy_objectCreation();
136 void jsIncDecNonObjectProperty();
137 void JSONparse();
138 void arraySort();
139 void lookupOnDisappearingProperty();
140 void arrayConcat();
141 void recursiveBoundFunctions();
142
143 void qRegExpInport_data();
144 void qRegExpInport();
145 void qRegularExpressionImport_data();
146 void qRegularExpressionImport();
147 void dateRoundtripJSQtJS();
148 void dateRoundtripQtJSQt();
149 void dateConversionJSQt();
150 void dateConversionQtJS();
151 void functionPrototypeExtensions();
152 void threadedEngine();
153
154 void functionDeclarationsInConditionals();
155
156 void arrayPop_QTBUG_35979();
157 void array_unshift_QTBUG_52065();
158 void array_join_QTBUG_53672();
159
160 void regexpLastMatch();
161 void regexpLastIndex();
162 void indexedAccesses();
163
164 void prototypeChainGc();
165 void prototypeChainGc_QTBUG38299();
166
167 void dynamicProperties();
168
169 void scopeOfEvaluate();
170
171 void callConstants();
172
173 void installTranslationFunctions();
174 void translateScript_data();
175 void translateScript();
176 void translateScript_crossScript();
177 void translateScript_trNoOp();
178 void translateScript_callQsTrFromCpp();
179 void translateWithInvalidArgs_data();
180 void translateWithInvalidArgs();
181 void translationContext_data();
182 void translationContext();
183 void translateScriptIdBased();
184 void translateScriptUnicode_data();
185 void translateScriptUnicode();
186 void translateScriptUnicodeIdBased_data();
187 void translateScriptUnicodeIdBased();
188 void translateFromBuiltinCallback();
189 void translationFilePath_data();
190 void translationFilePath();
191
192 void installConsoleFunctions();
193 void logging();
194 void tracing();
195 void asserts();
196 void exceptions();
197
198 void installGarbageCollectionFunctions();
199
200 void installAllExtensions();
201
202 void privateMethods();
203
204 void engineForObject();
205 void intConversion_QTBUG43309();
206#ifdef QT_DEPRECATED
207 void toFixed();
208#endif
209
210 void argumentEvaluationOrder();
211
212 void v4FunctionWithoutQML();
213
214 void withNoContext();
215 void holeInPropertyData();
216
217 void basicBlockMergeAfterLoopPeeling();
218
219 void modulusCrash();
220 void malformedExpression();
221
222 void scriptScopes();
223
224 void binaryNumbers();
225 void octalNumbers();
226
227 void incrementAfterNewline();
228
229 void deleteInsideForIn();
230
231 void functionToString_data();
232 void functionToString();
233
234 void stringReplace();
235
236 void protoChanges_QTBUG68369();
237 void multilineStrings();
238
239 void throwError();
240 void throwErrorObject();
241 void returnError();
242 void mathMinMax();
243
244 void importModule();
245 void importModuleRelative();
246 void importModuleWithLexicallyScopedVars();
247 void importExportErrors();
248
249 void equality();
250 void aggressiveGc();
251 void noAccumulatorInTemplateLiteral();
252
253 void interrupt_data();
254 void interrupt();
255
256 void triggerBackwardJumpWithDestructuring();
257 void arrayConcatOnSparseArray();
258 void sortSparseArray();
259 void compileBrokenRegexp();
260 void sortNonStringArray();
261 void iterateInvalidProxy();
262 void applyOnHugeArray();
263
264 void tostringRecursionCheck();
265 void arrayIncludesWithLargeArray();
266 void printCircularArray();
267 void typedArraySet();
268 void dataViewCtor();
269
270 void uiLanguage();
271
272public:
273 Q_INVOKABLE QJSValue throwingCppMethod1();
274 Q_INVOKABLE void throwingCppMethod2();
275 Q_INVOKABLE QJSValue throwingCppMethod3();
276
277signals:
278 void testSignal();
279};
280
281tst_QJSEngine::tst_QJSEngine()
282{
283}
284
285tst_QJSEngine::~tst_QJSEngine()
286{
287}
288
289Q_DECLARE_METATYPE(Qt::KeyboardModifier)
290Q_DECLARE_METATYPE(Qt::KeyboardModifiers)
291
292class OverloadedSlots : public QObject
293{
294 Q_OBJECT
295public:
296 OverloadedSlots()
297 {
298 }
299
300signals:
301 void slotWithoutArgCalled();
302 void slotWithSingleArgCalled(const QString &arg);
303 void slotWithArgumentsCalled(const QString &arg1, const QString &arg2, const QString &arg3);
304 void slotWithOverloadedArgumentsCalled(const QString &arg, Qt::KeyboardModifier modifier, Qt::KeyboardModifiers moreModifiers);
305 void slotWithTwoOverloadedArgumentsCalled(const QString &arg, Qt::KeyboardModifiers moreModifiers, Qt::KeyboardModifier modifier);
306
307public slots:
308 void slotToCall() { emit slotWithoutArgCalled(); }
309 void slotToCall(const QString &arg) { emit slotWithSingleArgCalled(arg); }
310 void slotToCall(const QString &arg, const QString &arg2, const QString &arg3 = QString())
311 {
312 slotWithArgumentsCalled(arg1: arg, arg2, arg3);
313 }
314 void slotToCall(const QString &arg, Qt::KeyboardModifier modifier, Qt::KeyboardModifiers blah = Qt::ShiftModifier)
315 {
316 emit slotWithOverloadedArgumentsCalled(arg, modifier, moreModifiers: blah);
317 }
318 void slotToCallTwoDefault(const QString &arg, Qt::KeyboardModifiers modifiers = Qt::ShiftModifier | Qt::ControlModifier, Qt::KeyboardModifier modifier = Qt::AltModifier)
319 {
320 emit slotWithTwoOverloadedArgumentsCalled(arg, moreModifiers: modifiers, modifier);
321 }
322};
323
324void tst_QJSEngine::callQObjectSlot()
325{
326 OverloadedSlots dummy;
327 QJSEngine eng;
328 eng.globalObject().setProperty(name: "dummy", value: eng.newQObject(object: &dummy));
329 QQmlEngine::setObjectOwnership(&dummy, QQmlEngine::CppOwnership);
330
331 {
332 QSignalSpy spy(&dummy, SIGNAL(slotWithoutArgCalled()));
333 eng.evaluate(program: "dummy.slotToCall();");
334 QCOMPARE(spy.count(), 1);
335 }
336
337 {
338 QSignalSpy spy(&dummy, SIGNAL(slotWithSingleArgCalled(QString)));
339 eng.evaluate(program: "dummy.slotToCall('arg');");
340
341 QCOMPARE(spy.count(), 1);
342 const QList<QVariant> arguments = spy.takeFirst();
343 QCOMPARE(arguments.at(0).toString(), QString("arg"));
344 }
345
346 {
347 QSignalSpy spy(&dummy, SIGNAL(slotWithArgumentsCalled(QString, QString, QString)));
348 eng.evaluate(program: "dummy.slotToCall('arg', 'arg2');");
349 QCOMPARE(spy.count(), 1);
350
351 const QList<QVariant> arguments = spy.takeFirst();
352 QCOMPARE(arguments.at(0).toString(), QString("arg"));
353 QCOMPARE(arguments.at(1).toString(), QString("arg2"));
354 QCOMPARE(arguments.at(2).toString(), QString());
355 }
356
357 {
358 QSignalSpy spy(&dummy, SIGNAL(slotWithArgumentsCalled(QString, QString, QString)));
359 eng.evaluate(program: "dummy.slotToCall('arg', 'arg2', 'arg3');");
360 QCOMPARE(spy.count(), 1);
361
362 const QList<QVariant> arguments = spy.takeFirst();
363 QCOMPARE(arguments.at(0).toString(), QString("arg"));
364 QCOMPARE(arguments.at(1).toString(), QString("arg2"));
365 QCOMPARE(arguments.at(2).toString(), QString("arg3"));
366 }
367
368 {
369 QSignalSpy spy(&dummy, SIGNAL(slotWithOverloadedArgumentsCalled(QString, Qt::KeyboardModifier, Qt::KeyboardModifiers)));
370 eng.evaluate(QStringLiteral("dummy.slotToCall('arg', %1);").arg(a: QString::number(Qt::ControlModifier)));
371 QCOMPARE(spy.count(), 1);
372
373 const QList<QVariant> arguments = spy.first();
374 QCOMPARE(arguments.at(0).toString(), QString("arg"));
375 QCOMPARE(arguments.at(1).toInt(), int(Qt::ControlModifier));
376 QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(2))), int(Qt::ShiftModifier));
377
378 }
379
380 {
381 QSignalSpy spy(&dummy, SIGNAL(slotWithTwoOverloadedArgumentsCalled(QString, Qt::KeyboardModifiers, Qt::KeyboardModifier)));
382 QJSValue v = eng.evaluate(QStringLiteral("dummy.slotToCallTwoDefault('arg', %1);").arg(a: QString::number(Qt::MetaModifier | Qt::KeypadModifier)));
383 QCOMPARE(spy.count(), 1);
384
385 const QList<QVariant> arguments = spy.first();
386 QCOMPARE(arguments.at(0).toString(), QString("arg"));
387 QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(1))), int(Qt::MetaModifier | Qt::KeypadModifier));
388 QCOMPARE(int(qvariant_cast<Qt::KeyboardModifier>(arguments.at(2))), int(Qt::AltModifier));
389 }
390
391 QJSValue jsArray = eng.newArray();
392 jsArray.setProperty(QStringLiteral("MetaModifier"), value: QJSValue(Qt::MetaModifier));
393 jsArray.setProperty(QStringLiteral("ShiftModifier"), value: QJSValue(Qt::ShiftModifier));
394 jsArray.setProperty(QStringLiteral("ControlModifier"), value: QJSValue(Qt::ControlModifier));
395 jsArray.setProperty(QStringLiteral("KeypadModifier"), value: QJSValue(Qt::KeypadModifier));
396
397 QJSValue value = eng.newQObject(object: new QObject);
398 value.setPrototype(jsArray);
399 eng.globalObject().setProperty(QStringLiteral("Qt"), value);
400
401 {
402 QSignalSpy spy(&dummy, SIGNAL(slotWithOverloadedArgumentsCalled(QString, Qt::KeyboardModifier, Qt::KeyboardModifiers)));
403 QJSValue v = eng.evaluate(QStringLiteral("dummy.slotToCall('arg', Qt.ControlModifier);"));
404 QCOMPARE(spy.count(), 1);
405
406 const QList<QVariant> arguments = spy.first();
407 QCOMPARE(arguments.at(0).toString(), QString("arg"));
408 QCOMPARE(arguments.at(1).toInt(), int(Qt::ControlModifier));
409 QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(2))), int(Qt::ShiftModifier));
410 }
411
412 {
413 QSignalSpy spy(&dummy, SIGNAL(slotWithTwoOverloadedArgumentsCalled(QString, Qt::KeyboardModifiers, Qt::KeyboardModifier)));
414 QJSValue v = eng.evaluate(QStringLiteral("dummy.slotToCallTwoDefault('arg', Qt.MetaModifier | Qt.KeypadModifier);"));
415 QCOMPARE(spy.count(), 1);
416
417 const QList<QVariant> arguments = spy.first();
418 QCOMPARE(arguments.at(0).toString(), QString("arg"));
419 QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(1))), int(Qt::MetaModifier | Qt::KeypadModifier));
420 QCOMPARE(int(qvariant_cast<Qt::KeyboardModifier>(arguments.at(2))), int(Qt::AltModifier));
421 }
422
423}
424
425void tst_QJSEngine::constructWithParent()
426{
427 QPointer<QJSEngine> ptr;
428 {
429 QObject obj;
430 QJSEngine *engine = new QJSEngine(&obj);
431 ptr = engine;
432 }
433 QVERIFY(ptr.isNull());
434}
435
436void tst_QJSEngine::newObject()
437{
438 QJSEngine eng;
439 QJSValue object = eng.newObject();
440 QVERIFY(!object.isUndefined());
441 QCOMPARE(object.isObject(), true);
442 QCOMPARE(object.isCallable(), false);
443 // prototype should be Object.prototype
444 QVERIFY(!object.prototype().isUndefined());
445 QCOMPARE(object.prototype().isObject(), true);
446 QCOMPARE(object.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true);
447}
448
449void tst_QJSEngine::newArray()
450{
451 QJSEngine eng;
452 QJSValue array = eng.newArray();
453 QVERIFY(!array.isUndefined());
454 QCOMPARE(array.isArray(), true);
455 QCOMPARE(array.isObject(), true);
456 QVERIFY(!array.isCallable());
457 // prototype should be Array.prototype
458 QVERIFY(!array.prototype().isUndefined());
459 QCOMPARE(array.prototype().isArray(), true);
460 QCOMPARE(array.prototype().strictlyEquals(eng.evaluate("Array.prototype")), true);
461}
462
463void tst_QJSEngine::newArray_HooliganTask218092()
464{
465 QJSEngine eng;
466 {
467 QJSValue ret = eng.evaluate(program: "[].splice(0, 0, 'a')");
468 QVERIFY(ret.isArray());
469 QCOMPARE(ret.property("length").toInt(), 0);
470 }
471 {
472 QJSValue ret = eng.evaluate(program: "['a'].splice(0, 1, 'b')");
473 QVERIFY(ret.isArray());
474 QCOMPARE(ret.property("length").toInt(), 1);
475 }
476 {
477 QJSValue ret = eng.evaluate(program: "['a', 'b'].splice(0, 1, 'c')");
478 QVERIFY(ret.isArray());
479 QCOMPARE(ret.property("length").toInt(), 1);
480 }
481 {
482 QJSValue ret = eng.evaluate(program: "['a', 'b', 'c'].splice(0, 2, 'd')");
483 QVERIFY(ret.isArray());
484 QCOMPARE(ret.property("length").toInt(), 2);
485 }
486 {
487 QJSValue ret = eng.evaluate(program: "['a', 'b', 'c'].splice(1, 2, 'd', 'e', 'f')");
488 QVERIFY(ret.isArray());
489 QCOMPARE(ret.property("length").toInt(), 2);
490 }
491}
492
493void tst_QJSEngine::newArray_HooliganTask233836()
494{
495 QJSEngine eng;
496 {
497 // According to ECMA-262, this should cause a RangeError.
498 QJSValue ret = eng.evaluate(program: "a = new Array(4294967295); a.push('foo')");
499 QVERIFY(ret.isError() && ret.toString().contains(QLatin1String("RangeError")));
500 }
501 {
502 QJSValue ret = eng.newArray(length: 0xFFFFFFFF);
503 QCOMPARE(ret.property("length").toUInt(), uint(0xFFFFFFFF));
504 ret.setProperty(arrayIndex: 0xFFFFFFFF, value: 123);
505 QCOMPARE(ret.property("length").toUInt(), uint(0xFFFFFFFF));
506 QVERIFY(ret.property(0xFFFFFFFF).isNumber());
507 QCOMPARE(ret.property(0xFFFFFFFF).toInt(), 123);
508 ret.setProperty(arrayIndex: 123, value: 456);
509 QCOMPARE(ret.property("length").toUInt(), uint(0xFFFFFFFF));
510 QVERIFY(ret.property(123).isNumber());
511 QCOMPARE(ret.property(123).toInt(), 456);
512 }
513}
514
515void tst_QJSEngine::toScriptValue_data()
516{
517 QTest::addColumn<QVariant>(name: "input");
518
519 QTest::newRow(dataTag: "UnknownType") << QVariant(int(QMetaType::UnknownType), nullptr);
520 QTest::newRow(dataTag: "Nullptr") << QVariant(int(QMetaType::Nullptr), nullptr);
521 QTest::newRow(dataTag: "true") << QVariant(true);
522 QTest::newRow(dataTag: "false") << QVariant(false);
523 QTest::newRow(dataTag: "int") << QVariant(int(42));
524 QTest::newRow(dataTag: "uint") << QVariant(uint(42));
525 QTest::newRow(dataTag: "longlong") << QVariant(qlonglong(4242));
526 QTest::newRow(dataTag: "ulonglong") << QVariant(qulonglong(4242));
527 QTest::newRow(dataTag: "double") << QVariant(double(42.42));
528 QTest::newRow(dataTag: "float") << QVariant(float(42.42));
529 QTest::newRow(dataTag: "qstring") << QVariant(QString::fromLatin1(str: "hello"));
530 QTest::newRow(dataTag: "qbytearray") << QVariant(QByteArray("hello"));
531 QTest::newRow(dataTag: "short") << QVariant(short('r'));
532 QTest::newRow(dataTag: "ushort") << QVariant(short('b'));
533 QTest::newRow(dataTag: "char") << QVariant(char('r'));
534 QTest::newRow(dataTag: "uchar") << QVariant(uchar('b'));
535 QTest::newRow(dataTag: "qchar") << QVariant(QString::fromUtf8(str: "Ã¥").at(i: 0));
536 QTest::newRow(dataTag: "qdate") << QVariant(QDate(1925, 5, 8));
537 QTest::newRow(dataTag: "qtime") << QVariant(QTime(4, 5, 6));
538 QTest::newRow(dataTag: "qregularexpression") << QVariant(QRegularExpression(".*"));
539 QTest::newRow(dataTag: "qpointf") << QVariant(QPointF(42, 24));
540 QTest::newRow(dataTag: "qvariantlist") << QVariant(QVariantList() << 42.24 << 5 << "hello");
541 QTest::newRow(dataTag: "qvariantlist_point") << QVariant(QVariantList() << 42.24 << QPointF(42.24, 24.42) << QPointF(24.42, 42.24));
542 QVariantMap vm; vm.insert(key: "test", value: 55); vm.insert(key: "abc", value: 42.42);;
543 QTest::newRow(dataTag: "qvariantmap") << QVariant(vm);
544 vm.clear(); vm.insert(key: "point1", value: QPointF(42.24, 24.42)); vm.insert(key: "point2", value: QPointF(42.24, 24.42));
545 QTest::newRow(dataTag: "qvariantmap_point") << QVariant(vm);
546 QTest::newRow(dataTag: "qvariant") << QVariant(QVariant(42));
547 QTest::newRow(dataTag: "QList<int>") << QVariant::fromValue(value: QList<int>() << 1 << 2 << 3 << 4);
548 QTest::newRow(dataTag: "QVector<int>") << QVariant::fromValue(value: QVector<int>() << 1 << 2 << 3 << 4);
549 QTest::newRow(dataTag: "QList<QString>") << QVariant::fromValue(value: QVector<QString>() << "1" << "2" << "3" << "4");
550 QTest::newRow(dataTag: "QStringList") << QVariant::fromValue(value: QStringList() << "1" << "2" << "3" << "4");
551 QTest::newRow(dataTag: "QMap<QString, QString>") << QVariant::fromValue(value: QMap<QString, QString>{{ "1", "2" }, { "3", "4" }});
552 QTest::newRow(dataTag: "QHash<QString, QString>") << QVariant::fromValue(value: QHash<QString, QString>{{ "1", "2" }, { "3", "4" }});
553 QTest::newRow(dataTag: "QMap<QString, QPointF>") << QVariant::fromValue(value: QMap<QString, QPointF>{{ "1", { 42.24, 24.42 } }, { "3", { 24.42, 42.24 } }});
554 QTest::newRow(dataTag: "QHash<QString, QPointF>") << QVariant::fromValue(value: QHash<QString, QPointF>{{ "1", { 42.24, 24.42 } }, { "3", { 24.42, 42.24 } }});
555}
556
557void tst_QJSEngine::toScriptValue()
558{
559 QFETCH(QVariant, input);
560
561 QJSEngine engine;
562 QJSValue outputJS = engine.toScriptValue(value: input);
563 QVariant output = engine.fromScriptValue<QVariant>(value: outputJS);
564
565 QCOMPARE(input, output);
566}
567
568void tst_QJSEngine::toScriptValuenotroundtripped_data()
569{
570 QTest::addColumn<QVariant>(name: "input");
571 QTest::addColumn<QVariant>(name: "output");
572
573 QTest::newRow(dataTag: "QList<QObject*>") << QVariant::fromValue(value: QList<QObject*>() << this) << QVariant(QVariantList() << QVariant::fromValue(value: this));
574 QTest::newRow(dataTag: "QObjectList") << QVariant::fromValue(value: QObjectList() << this) << QVariant(QVariantList() << QVariant::fromValue(value: this));
575 QTest::newRow(dataTag: "QList<QPoint>") << QVariant::fromValue(value: QList<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42));
576 QTest::newRow(dataTag: "QVector<QPoint>") << QVariant::fromValue(value: QVector<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42));
577 QTest::newRow(dataTag: "VoidStar") << QVariant(int(QMetaType::VoidStar), nullptr) << QVariant(int(QMetaType::Nullptr), nullptr);
578 QTest::newRow(dataTag: "qregex") << QVariant(QRegExp(".*", Qt::CaseSensitive, QRegExp::RegExp2)) << QVariant(QRegularExpression(".*"));
579}
580
581// This is almost the same as toScriptValue, but the inputs don't roundtrip to
582// exactly the same value.
583void tst_QJSEngine::toScriptValuenotroundtripped()
584{
585 QFETCH(QVariant, input);
586 QFETCH(QVariant, output);
587
588 QJSEngine engine;
589 QJSValue outputJS = engine.toScriptValue(value: input);
590 QVariant actualOutput = engine.fromScriptValue<QVariant>(value: outputJS);
591
592 QCOMPARE(actualOutput, output);
593}
594
595void tst_QJSEngine::newVariant()
596{
597 QJSEngine eng;
598 {
599 QJSValue opaque = eng.toScriptValue(value: QVariant(QPoint(1, 2)));
600 QVERIFY(!opaque.isUndefined());
601 QCOMPARE(opaque.isVariant(), false);
602 QVERIFY(!opaque.isCallable());
603 QCOMPARE(opaque.isObject(), true);
604 QVERIFY(!opaque.prototype().isUndefined());
605 QCOMPARE(opaque.prototype().isVariant(), false);
606 QVERIFY(opaque.property("valueOf").callWithInstance(opaque).equals(opaque));
607 }
608}
609
610void tst_QJSEngine::newVariant_valueOfToString()
611{
612 // valueOf() and toString()
613 QJSEngine eng;
614 {
615 QJSValue object = eng.toScriptValue(value: QVariant(QPoint(10, 20)));
616 QJSValue value = object.property(name: "valueOf").callWithInstance(instance: object);
617 QVERIFY(value.isObject());
618 QVERIFY(value.strictlyEquals(object));
619 QCOMPARE(object.toString(), QString::fromLatin1("QPoint(10, 20)"));
620 }
621}
622
623void tst_QJSEngine::newVariant_valueOfEnum()
624{
625 QJSEngine eng;
626 {
627 QJSValue object = eng.toScriptValue(value: QVariant::fromValue(value: Qt::ControlModifier));
628 QJSValue value = object.property(name: "valueOf").callWithInstance(instance: object);
629 QVERIFY(value.isNumber());
630 QCOMPARE(value.toInt(), static_cast<qint32>(Qt::ControlModifier));
631 }
632}
633
634void tst_QJSEngine::newRegExp()
635{
636 QJSEngine eng;
637 QJSValue rexps[] = {
638 eng.toScriptValue(value: QRegularExpression("foo")),
639 eng.toScriptValue(value: QRegExp("foo"))
640 };
641 for (const auto &rexp : rexps) {
642 QVERIFY(!rexp.isUndefined());
643 QCOMPARE(rexp.isRegExp(), true);
644 QCOMPARE(rexp.isObject(), true);
645 QCOMPARE(rexp.isCallable(), false);
646 // prototype should be RegExp.prototype
647 QVERIFY(!rexp.prototype().isUndefined());
648 QCOMPARE(rexp.prototype().isObject(), true);
649 // Get [[Class]] internal property of RegExp Prototype Object.
650 // See ECMA-262 Section 8.6.2, "Object Internal Properties and Methods".
651 // See ECMA-262 Section 15.10.6, "Properties of the RegExp Prototype Object".
652 QJSValue r = eng.evaluate(program: "Object.prototype.toString.call(RegExp.prototype)");
653 QCOMPARE(r.toString(), QString::fromLatin1("[object Object]"));
654 QCOMPARE(rexp.prototype().strictlyEquals(eng.evaluate("RegExp.prototype")), true);
655
656 QCOMPARE(qjsvalue_cast<QRegExp>(rexp).pattern(), QRegExp("foo").pattern());
657 }
658}
659
660void tst_QJSEngine::jsRegExp()
661{
662 // See ECMA-262 Section 15.10, "RegExp Objects".
663 // These should really be JS-only tests, as they test the implementation's
664 // ECMA-compliance, not the C++ API. Compliance should already be covered
665 // by the Mozilla tests (qscriptjstestsuite).
666 // We can consider updating the expected results of this test if the
667 // RegExp implementation changes.
668
669 QJSEngine eng;
670 QJSValue r = eng.evaluate(program: "/foo/gim");
671 QVERIFY(r.isRegExp());
672 QCOMPARE(r.toString(), QString::fromLatin1("/foo/gim"));
673
674 QJSValue rxCtor = eng.globalObject().property(name: "RegExp");
675 QJSValue r2 = rxCtor.call(args: QJSValueList() << r);
676 QVERIFY(r2.isRegExp());
677 QVERIFY(r2.strictlyEquals(r));
678
679 QJSValue r3 = rxCtor.call(args: QJSValueList() << r << "gim");
680 QVERIFY(!r3.isError());
681
682 QJSValue r4 = rxCtor.call(args: QJSValueList() << "foo" << "gim");
683 QVERIFY(r4.isRegExp());
684
685 QJSValue r5 = rxCtor.callAsConstructor(args: QJSValueList() << r);
686 QVERIFY(r5.isRegExp());
687 QCOMPARE(r5.toString(), QString::fromLatin1("/foo/gim"));
688 QVERIFY(r5.strictlyEquals(r));
689
690 // See ECMA-262 Section 15.10.4.1, "new RegExp(pattern, flags)".
691 QJSValue r6 = rxCtor.callAsConstructor(args: QJSValueList() << "foo" << "bar");
692 QVERIFY(r6.isError());
693 QVERIFY(r6.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag
694
695 QJSValue r7 = eng.evaluate(program: "/foo/gimp");
696 QVERIFY(r7.isError());
697 QVERIFY(r7.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag
698
699 QJSValue r8 = eng.evaluate(program: "/foo/migmigmig");
700 QVERIFY(r8.isError());
701 QVERIFY(r8.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag
702
703 QJSValue r9 = rxCtor.callAsConstructor();
704 QVERIFY(r9.isRegExp());
705 QCOMPARE(r9.toString(), QString::fromLatin1("/(?:)/"));
706
707 QJSValue r10 = rxCtor.callAsConstructor(args: QJSValueList() << "" << "gim");
708 QVERIFY(r10.isRegExp());
709 QCOMPARE(r10.toString(), QString::fromLatin1("/(?:)/gim"));
710
711 QJSValue r11 = rxCtor.callAsConstructor(args: QJSValueList() << "{1.*}" << "g");
712 QVERIFY(r11.isRegExp());
713 QCOMPARE(r11.toString(), QString::fromLatin1("/{1.*}/g"));
714}
715
716void tst_QJSEngine::newDate()
717{
718 QJSEngine eng;
719
720 {
721 QJSValue date = eng.evaluate(program: "new Date(0)");
722 QVERIFY(!date.isUndefined());
723 QCOMPARE(date.isDate(), true);
724 QCOMPARE(date.isObject(), true);
725 QVERIFY(!date.isCallable());
726 }
727
728 {
729 QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::LocalTime);
730 QJSValue date = eng.toScriptValue(value: dt);
731 QVERIFY(!date.isUndefined());
732 QCOMPARE(date.isDate(), true);
733 QCOMPARE(date.isObject(), 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 QJSValue date = eng.toScriptValue(value: dt);
741 // toDateTime() result should be in local time
742 QCOMPARE(date.toDateTime(), dt.toLocalTime());
743 }
744}
745
746void tst_QJSEngine::jsParseDate()
747{
748 QJSEngine eng;
749 // Date.parse() should return NaN when it fails
750 {
751 QJSValue ret = eng.evaluate(program: "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 {
758 QJSValue ret = eng.evaluate(program: "var x = new Date(); var s = x.toString(); s == new Date(Date.parse(s)).toString()");
759 QVERIFY(ret.isBool());
760 QCOMPARE(ret.toBool(), true);
761 }
762}
763
764void tst_QJSEngine::newQObject()
765{
766 QJSEngine eng;
767 QObject temp;
768
769 {
770 QJSValue qobject = eng.newQObject(object: nullptr);
771 QCOMPARE(qobject.isNull(), true);
772 QCOMPARE(qobject.isObject(), false);
773 QCOMPARE(qobject.toQObject(), (QObject *)nullptr);
774 }
775 {
776 QJSValue qobject = eng.newQObject(object: &temp);
777 QVERIFY(!qobject.isUndefined());
778 QCOMPARE(qobject.isQObject(), true);
779 QCOMPARE(qobject.isObject(), true);
780 QCOMPARE(qobject.toQObject(), (QObject *)&temp);
781 QVERIFY(!qobject.isCallable());
782 // prototype should be QObject.prototype
783 QCOMPARE(qobject.prototype().isObject(), true);
784 QEXPECT_FAIL("", "FIXME: newly created QObject's prototype is an JS Object", Continue);
785 QCOMPARE(qobject.prototype().isQObject(), true);
786 }
787}
788
789void tst_QJSEngine::newQObjectRace()
790{
791 class Thread : public QThread
792 {
793 void run() override
794 {
795 for (int i=0;i<100;++i)
796 {
797 QJSEngine e;
798 auto obj = e.newQObject(object: new QObject);
799 }
800 }
801 };
802
803
804 Thread threads[8];
805 for (auto& t : threads)
806 t.start(); // should not crash
807 for (auto& t : threads)
808 t.wait();
809}
810
811void tst_QJSEngine::newQObject_ownership()
812{
813 QJSEngine eng;
814 {
815 QPointer<QObject> ptr = new QObject();
816 QVERIFY(ptr != nullptr);
817 {
818 QJSValue v = eng.newQObject(object: ptr);
819 }
820 eng.collectGarbage();
821 if (ptr)
822 QGuiApplication::sendPostedEvents(receiver: ptr, event_type: QEvent::DeferredDelete);
823 QVERIFY(ptr.isNull());
824 }
825 {
826 QPointer<QObject> ptr = new QObject(this);
827 QVERIFY(ptr != nullptr);
828 {
829 QJSValue v = eng.newQObject(object: ptr);
830 }
831 QObject *before = ptr;
832 eng.collectGarbage();
833 QCOMPARE(ptr.data(), before);
834 delete ptr;
835 }
836 {
837 QObject *parent = new QObject();
838 QObject *child = new QObject(parent);
839 QJSValue v = eng.newQObject(object: child);
840 QCOMPARE(v.toQObject(), child);
841 delete parent;
842 QCOMPARE(v.toQObject(), (QObject *)nullptr);
843 }
844 {
845 QPointer<QObject> ptr = new QObject();
846 QVERIFY(ptr != nullptr);
847 {
848 QJSValue v = eng.newQObject(object: ptr);
849 }
850 eng.collectGarbage();
851 // no parent, so it should be like ScriptOwnership
852 if (ptr)
853 QGuiApplication::sendPostedEvents(receiver: ptr, event_type: QEvent::DeferredDelete);
854 QVERIFY(ptr.isNull());
855 }
856 {
857 QObject *parent = new QObject();
858 QPointer<QObject> child = new QObject(parent);
859 QVERIFY(child != nullptr);
860 {
861 QJSValue v = eng.newQObject(object: child);
862 }
863 eng.collectGarbage();
864 // has parent, so it should be like QtOwnership
865 QVERIFY(child != nullptr);
866 delete parent;
867 }
868 {
869 QPointer<QObject> ptr = new QObject();
870 QVERIFY(ptr != nullptr);
871 {
872 QQmlEngine::setObjectOwnership(ptr.data(), QQmlEngine::CppOwnership);
873 QJSValue v = eng.newQObject(object: ptr);
874 }
875 eng.collectGarbage();
876 if (ptr)
877 QGuiApplication::sendPostedEvents(receiver: ptr, event_type: QEvent::DeferredDelete);
878 QVERIFY(!ptr.isNull());
879 delete ptr.data();
880 }
881}
882
883void tst_QJSEngine::newQObject_deletedEngine()
884{
885 QJSValue object;
886 QObject *ptr = new QObject();
887 QSignalSpy spy(ptr, SIGNAL(destroyed()));
888 {
889 QJSEngine engine;
890 object = engine.newQObject(object: ptr);
891 engine.globalObject().setProperty(name: "obj", value: object);
892 }
893 QTRY_VERIFY(spy.count());
894}
895
896class TestQMetaObject : public QObject {
897 Q_OBJECT
898 Q_PROPERTY(int called READ called)
899public:
900 enum Enum1 {
901 Zero = 0,
902 One,
903 Two
904 };
905 enum Enum2 {
906 A = 0,
907 B,
908 C
909 };
910 Q_ENUMS(Enum1 Enum2)
911
912 Q_INVOKABLE TestQMetaObject() {}
913 Q_INVOKABLE TestQMetaObject(int)
914 : m_called(2) {}
915 Q_INVOKABLE TestQMetaObject(QString)
916 : m_called(3) {}
917 Q_INVOKABLE TestQMetaObject(QString, int)
918 : m_called(4) {}
919 int called() const {
920 return m_called;
921 }
922private:
923 int m_called = 1;
924};
925
926void tst_QJSEngine::newQObjectPropertyCache()
927{
928 QScopedPointer<QObject> obj(new QObject);
929 QQmlEngine::setObjectOwnership(obj.data(), QQmlEngine::CppOwnership);
930
931 {
932 QJSEngine engine;
933 engine.newQObject(object: obj.data());
934 QVERIFY(QQmlData::get(obj.data())->propertyCache);
935 }
936 QVERIFY(!QQmlData::get(obj.data())->propertyCache);
937}
938
939void tst_QJSEngine::newQMetaObject() {
940 {
941 QJSEngine engine;
942 QJSValue metaObject = engine.newQMetaObject(metaObject: &TestQMetaObject::staticMetaObject);
943 QCOMPARE(metaObject.isNull(), false);
944 QCOMPARE(metaObject.isObject(), true);
945 QCOMPARE(metaObject.isQObject(), false);
946 QCOMPARE(metaObject.isCallable(), true);
947 QCOMPARE(metaObject.isQMetaObject(), true);
948
949 QCOMPARE(metaObject.toQMetaObject(), &TestQMetaObject::staticMetaObject);
950
951 QVERIFY(metaObject.strictlyEquals(engine.newQMetaObject<TestQMetaObject>()));
952
953
954 {
955 auto result = metaObject.callAsConstructor();
956 if (result.isError())
957 qDebug() << result.toString();
958 QCOMPARE(result.isError(), false);
959 QCOMPARE(result.isNull(), false);
960 QCOMPARE(result.isObject(), true);
961 QCOMPARE(result.isQObject(), true);
962 QVERIFY(result.property("constructor").strictlyEquals(metaObject));
963 QVERIFY(result.prototype().strictlyEquals(metaObject));
964
965
966 QCOMPARE(result.property("called").toInt(), 1);
967
968 }
969
970 QJSValue integer(42);
971 QJSValue string("foo");
972
973 {
974 auto result = metaObject.callAsConstructor(args: {integer});
975 QCOMPARE(result.property("called").toInt(), 2);
976 }
977
978 {
979 auto result = metaObject.callAsConstructor(args: {string});
980 QCOMPARE(result.property("called").toInt(), 3);
981 }
982
983 {
984 auto result = metaObject.callAsConstructor(args: {string, integer});
985 QCOMPARE(result.property("called").toInt(), 4);
986 }
987 }
988
989 {
990 QJSEngine engine;
991 QJSValue metaObject = engine.newQMetaObject(metaObject: &TestQMetaObject::staticMetaObject);
992 QCOMPARE(metaObject.property("Zero").toInt(), 0);
993 QCOMPARE(metaObject.property("One").toInt(), 1);
994 QCOMPARE(metaObject.property("Two").toInt(), 2);
995 QCOMPARE(metaObject.property("A").toInt(), 0);
996 QCOMPARE(metaObject.property("B").toInt(), 1);
997 QCOMPARE(metaObject.property("C").toInt(), 2);
998 }
999
1000}
1001
1002void tst_QJSEngine::exceptionInSlot()
1003{
1004 QJSEngine engine;
1005 QJSValue wrappedThis = engine.newQObject(object: this);
1006 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
1007 engine.globalObject().setProperty(name: "testCase", value: wrappedThis);
1008 engine.evaluate(
1009 program: "var called = false\n"
1010 "function throwingSlot() {\n"
1011 " called = true\n"
1012 " throw 42;\n"
1013 "}\n"
1014 "testCase.testSignal.connect(throwingSlot)\n"
1015 );
1016 QCOMPARE(engine.globalObject().property("called").toBool(), false);
1017 emit testSignal();
1018 QCOMPARE(engine.globalObject().property("called").toBool(), true);
1019}
1020
1021void tst_QJSEngine::globalObjectProperties()
1022{
1023 // See ECMA-262 Section 15.1, "The Global Object".
1024
1025 QJSEngine eng;
1026 QJSValue global = eng.globalObject();
1027
1028 QVERIFY(global.property("NaN").isNumber());
1029 QVERIFY(qIsNaN(global.property("NaN").toNumber()));
1030
1031 QVERIFY(global.property("Infinity").isNumber());
1032 QVERIFY(qIsInf(global.property("Infinity").toNumber()));
1033
1034 QVERIFY(global.property("undefined").isUndefined());
1035
1036 QVERIFY(global.property("eval").isCallable());
1037
1038 QVERIFY(global.property("parseInt").isCallable());
1039
1040 QVERIFY(global.property("parseFloat").isCallable());
1041
1042 QVERIFY(global.property("isNaN").isCallable());
1043
1044 QVERIFY(global.property("isFinite").isCallable());
1045
1046 QVERIFY(global.property("decodeURI").isCallable());
1047
1048 QVERIFY(global.property("decodeURIComponent").isCallable());
1049
1050 QVERIFY(global.property("encodeURI").isCallable());
1051
1052 QVERIFY(global.property("encodeURIComponent").isCallable());
1053
1054 QVERIFY(global.property("Object").isCallable());
1055 QVERIFY(global.property("Function").isCallable());
1056 QVERIFY(global.property("Array").isCallable());
1057 QVERIFY(global.property("String").isCallable());
1058 QVERIFY(global.property("Boolean").isCallable());
1059 QVERIFY(global.property("Number").isCallable());
1060 QVERIFY(global.property("Date").isCallable());
1061 QVERIFY(global.property("RegExp").isCallable());
1062 QVERIFY(global.property("Error").isCallable());
1063 QVERIFY(global.property("EvalError").isCallable());
1064 QVERIFY(global.property("RangeError").isCallable());
1065 QVERIFY(global.property("ReferenceError").isCallable());
1066 QVERIFY(global.property("SyntaxError").isCallable());
1067 QVERIFY(global.property("TypeError").isCallable());
1068 QVERIFY(global.property("URIError").isCallable());
1069 QVERIFY(global.property("Math").isObject());
1070 QVERIFY(!global.property("Math").isCallable());
1071}
1072
1073void tst_QJSEngine::globalObjectEquals()
1074{
1075 QJSEngine eng;
1076 QJSValue o = eng.globalObject();
1077 QVERIFY(o.strictlyEquals(eng.globalObject()));
1078 QVERIFY(o.equals(eng.globalObject()));
1079}
1080
1081void tst_QJSEngine::globalObjectProperties_enumerate()
1082{
1083 QJSEngine eng;
1084 QJSValue global = eng.globalObject();
1085
1086 QSet<QString> expectedNames;
1087 expectedNames
1088 << "isNaN"
1089 << "parseFloat"
1090 << "String"
1091 << "EvalError"
1092 << "URIError"
1093 << "Math"
1094 << "encodeURIComponent"
1095 << "RangeError"
1096 << "eval"
1097 << "isFinite"
1098 << "ReferenceError"
1099 << "Infinity"
1100 << "Function"
1101 << "RegExp"
1102 << "Number"
1103 << "parseInt"
1104 << "Object"
1105 << "decodeURI"
1106 << "TypeError"
1107 << "Boolean"
1108 << "encodeURI"
1109 << "NaN"
1110 << "Error"
1111 << "decodeURIComponent"
1112 << "Date"
1113 << "Array"
1114 << "Symbol"
1115 << "escape"
1116 << "unescape"
1117 << "SyntaxError"
1118 << "undefined"
1119 << "JSON"
1120 << "ArrayBuffer"
1121 << "SharedArrayBuffer"
1122 << "DataView"
1123 << "Int8Array"
1124 << "Uint8Array"
1125 << "Uint8ClampedArray"
1126 << "Int16Array"
1127 << "Uint16Array"
1128 << "Int32Array"
1129 << "Uint32Array"
1130 << "Float32Array"
1131 << "Float64Array"
1132 << "WeakSet"
1133 << "Set"
1134 << "WeakMap"
1135 << "Map"
1136 << "Reflect"
1137 << "Proxy"
1138 << "Atomics"
1139 << "Promise"
1140 ;
1141 QSet<QString> actualNames;
1142 {
1143 QJSValueIterator it(global);
1144 while (it.hasNext()) {
1145 it.next();
1146 actualNames.insert(value: it.name());
1147 }
1148 }
1149
1150 QSet<QString> remainingNames = actualNames;
1151 {
1152 QSet<QString>::const_iterator it;
1153 for (it = expectedNames.constBegin(); it != expectedNames.constEnd(); ++it) {
1154 QString name = *it;
1155 QVERIFY(actualNames.contains(name));
1156 remainingNames.remove(value: name);
1157 }
1158 }
1159 QVERIFY(remainingNames.isEmpty());
1160}
1161
1162void tst_QJSEngine::createGlobalObjectProperty()
1163{
1164 QJSEngine eng;
1165 QJSValue global = eng.globalObject();
1166 // create property with no attributes
1167 {
1168 QString name = QString::fromLatin1(str: "foo");
1169 QVERIFY(global.property(name).isUndefined());
1170 QJSValue val(123);
1171 global.setProperty(name, value: val);
1172 QVERIFY(global.property(name).equals(val));
1173 global.deleteProperty(name);
1174 QVERIFY(global.property(name).isUndefined());
1175 }
1176}
1177
1178void tst_QJSEngine::globalObjectWithCustomPrototype()
1179{
1180 QJSEngine engine;
1181 QJSValue proto = engine.newObject();
1182 proto.setProperty(name: "protoProperty", value: 123);
1183 QJSValue global = engine.globalObject();
1184 QJSValue originalProto = global.prototype();
1185 global.setPrototype(proto);
1186 {
1187 QJSValue ret = engine.evaluate(program: "protoProperty");
1188 QVERIFY(ret.isNumber());
1189 QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
1190 }
1191 {
1192 QJSValue ret = engine.evaluate(program: "this.protoProperty");
1193 QVERIFY(ret.isNumber());
1194 QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
1195 }
1196 {
1197 QJSValue ret = engine.evaluate(program: "this.hasOwnProperty('protoProperty')");
1198 QVERIFY(ret.isBool());
1199 QVERIFY(!ret.toBool());
1200 }
1201
1202 // Custom prototype set from JS
1203 {
1204 QJSValue ret = engine.evaluate(program: "this.__proto__ = { 'a': 123 }; a");
1205 QVERIFY(ret.isNumber());
1206 QVERIFY(ret.strictlyEquals(global.property("a")));
1207 }
1208}
1209
1210void tst_QJSEngine::builtinFunctionNames_data()
1211{
1212 QTest::addColumn<QString>(name: "expression");
1213 QTest::addColumn<QString>(name: "expectedName");
1214
1215 // See ECMA-262 Chapter 15, "Standard Built-in ECMAScript Objects".
1216
1217 QTest::newRow(dataTag: "parseInt") << QString("parseInt") << QString("parseInt");
1218 QTest::newRow(dataTag: "parseFloat") << QString("parseFloat") << QString("parseFloat");
1219 QTest::newRow(dataTag: "isNaN") << QString("isNaN") << QString("isNaN");
1220 QTest::newRow(dataTag: "isFinite") << QString("isFinite") << QString("isFinite");
1221 QTest::newRow(dataTag: "decodeURI") << QString("decodeURI") << QString("decodeURI");
1222 QTest::newRow(dataTag: "decodeURIComponent") << QString("decodeURIComponent") << QString("decodeURIComponent");
1223 QTest::newRow(dataTag: "encodeURI") << QString("encodeURI") << QString("encodeURI");
1224 QTest::newRow(dataTag: "encodeURIComponent") << QString("encodeURIComponent") << QString("encodeURIComponent");
1225 QTest::newRow(dataTag: "escape") << QString("escape") << QString("escape");
1226 QTest::newRow(dataTag: "unescape") << QString("unescape") << QString("unescape");
1227
1228 QTest::newRow(dataTag: "Array") << QString("Array") << QString("Array");
1229 QTest::newRow(dataTag: "Array.prototype.toString") << QString("Array.prototype.toString") << QString("toString");
1230 QTest::newRow(dataTag: "Array.prototype.toLocaleString") << QString("Array.prototype.toLocaleString") << QString("toLocaleString");
1231 QTest::newRow(dataTag: "Array.prototype.concat") << QString("Array.prototype.concat") << QString("concat");
1232 QTest::newRow(dataTag: "Array.prototype.find") << QString("Array.prototype.find") << QString("find");
1233 QTest::newRow(dataTag: "Array.prototype.findIndex") << QString("Array.prototype.findIndex") << QString("findIndex");
1234 QTest::newRow(dataTag: "Array.prototype.join") << QString("Array.prototype.join") << QString("join");
1235 QTest::newRow(dataTag: "Array.prototype.pop") << QString("Array.prototype.pop") << QString("pop");
1236 QTest::newRow(dataTag: "Array.prototype.push") << QString("Array.prototype.push") << QString("push");
1237 QTest::newRow(dataTag: "Array.prototype.reverse") << QString("Array.prototype.reverse") << QString("reverse");
1238 QTest::newRow(dataTag: "Array.prototype.shift") << QString("Array.prototype.shift") << QString("shift");
1239 QTest::newRow(dataTag: "Array.prototype.slice") << QString("Array.prototype.slice") << QString("slice");
1240 QTest::newRow(dataTag: "Array.prototype.sort") << QString("Array.prototype.sort") << QString("sort");
1241 QTest::newRow(dataTag: "Array.prototype.splice") << QString("Array.prototype.splice") << QString("splice");
1242 QTest::newRow(dataTag: "Array.prototype.unshift") << QString("Array.prototype.unshift") << QString("unshift");
1243
1244 QTest::newRow(dataTag: "Boolean") << QString("Boolean") << QString("Boolean");
1245 QTest::newRow(dataTag: "Boolean.prototype.toString") << QString("Boolean.prototype.toString") << QString("toString");
1246
1247 QTest::newRow(dataTag: "Date") << QString("Date") << QString("Date");
1248 QTest::newRow(dataTag: "Date.prototype.toString") << QString("Date.prototype.toString") << QString("toString");
1249 QTest::newRow(dataTag: "Date.prototype.toDateString") << QString("Date.prototype.toDateString") << QString("toDateString");
1250 QTest::newRow(dataTag: "Date.prototype.toTimeString") << QString("Date.prototype.toTimeString") << QString("toTimeString");
1251 QTest::newRow(dataTag: "Date.prototype.toLocaleString") << QString("Date.prototype.toLocaleString") << QString("toLocaleString");
1252 QTest::newRow(dataTag: "Date.prototype.toLocaleDateString") << QString("Date.prototype.toLocaleDateString") << QString("toLocaleDateString");
1253 QTest::newRow(dataTag: "Date.prototype.toLocaleTimeString") << QString("Date.prototype.toLocaleTimeString") << QString("toLocaleTimeString");
1254 QTest::newRow(dataTag: "Date.prototype.valueOf") << QString("Date.prototype.valueOf") << QString("valueOf");
1255 QTest::newRow(dataTag: "Date.prototype.getTime") << QString("Date.prototype.getTime") << QString("getTime");
1256 QTest::newRow(dataTag: "Date.prototype.getYear") << QString("Date.prototype.getYear") << QString("getYear");
1257 QTest::newRow(dataTag: "Date.prototype.getFullYear") << QString("Date.prototype.getFullYear") << QString("getFullYear");
1258 QTest::newRow(dataTag: "Date.prototype.getUTCFullYear") << QString("Date.prototype.getUTCFullYear") << QString("getUTCFullYear");
1259 QTest::newRow(dataTag: "Date.prototype.getMonth") << QString("Date.prototype.getMonth") << QString("getMonth");
1260 QTest::newRow(dataTag: "Date.prototype.getUTCMonth") << QString("Date.prototype.getUTCMonth") << QString("getUTCMonth");
1261 QTest::newRow(dataTag: "Date.prototype.getDate") << QString("Date.prototype.getDate") << QString("getDate");
1262 QTest::newRow(dataTag: "Date.prototype.getUTCDate") << QString("Date.prototype.getUTCDate") << QString("getUTCDate");
1263 QTest::newRow(dataTag: "Date.prototype.getDay") << QString("Date.prototype.getDay") << QString("getDay");
1264 QTest::newRow(dataTag: "Date.prototype.getUTCDay") << QString("Date.prototype.getUTCDay") << QString("getUTCDay");
1265 QTest::newRow(dataTag: "Date.prototype.getHours") << QString("Date.prototype.getHours") << QString("getHours");
1266 QTest::newRow(dataTag: "Date.prototype.getUTCHours") << QString("Date.prototype.getUTCHours") << QString("getUTCHours");
1267 QTest::newRow(dataTag: "Date.prototype.getMinutes") << QString("Date.prototype.getMinutes") << QString("getMinutes");
1268 QTest::newRow(dataTag: "Date.prototype.getUTCMinutes") << QString("Date.prototype.getUTCMinutes") << QString("getUTCMinutes");
1269 QTest::newRow(dataTag: "Date.prototype.getSeconds") << QString("Date.prototype.getSeconds") << QString("getSeconds");
1270 QTest::newRow(dataTag: "Date.prototype.getUTCSeconds") << QString("Date.prototype.getUTCSeconds") << QString("getUTCSeconds");
1271 QTest::newRow(dataTag: "Date.prototype.getMilliseconds") << QString("Date.prototype.getMilliseconds") << QString("getMilliseconds");
1272 QTest::newRow(dataTag: "Date.prototype.getUTCMilliseconds") << QString("Date.prototype.getUTCMilliseconds") << QString("getUTCMilliseconds");
1273 QTest::newRow(dataTag: "Date.prototype.getTimezoneOffset") << QString("Date.prototype.getTimezoneOffset") << QString("getTimezoneOffset");
1274 QTest::newRow(dataTag: "Date.prototype.setTime") << QString("Date.prototype.setTime") << QString("setTime");
1275 QTest::newRow(dataTag: "Date.prototype.setMilliseconds") << QString("Date.prototype.setMilliseconds") << QString("setMilliseconds");
1276 QTest::newRow(dataTag: "Date.prototype.setUTCMilliseconds") << QString("Date.prototype.setUTCMilliseconds") << QString("setUTCMilliseconds");
1277 QTest::newRow(dataTag: "Date.prototype.setSeconds") << QString("Date.prototype.setSeconds") << QString("setSeconds");
1278 QTest::newRow(dataTag: "Date.prototype.setUTCSeconds") << QString("Date.prototype.setUTCSeconds") << QString("setUTCSeconds");
1279 QTest::newRow(dataTag: "Date.prototype.setMinutes") << QString("Date.prototype.setMinutes") << QString("setMinutes");
1280 QTest::newRow(dataTag: "Date.prototype.setUTCMinutes") << QString("Date.prototype.setUTCMinutes") << QString("setUTCMinutes");
1281 QTest::newRow(dataTag: "Date.prototype.setHours") << QString("Date.prototype.setHours") << QString("setHours");
1282 QTest::newRow(dataTag: "Date.prototype.setUTCHours") << QString("Date.prototype.setUTCHours") << QString("setUTCHours");
1283 QTest::newRow(dataTag: "Date.prototype.setDate") << QString("Date.prototype.setDate") << QString("setDate");
1284 QTest::newRow(dataTag: "Date.prototype.setUTCDate") << QString("Date.prototype.setUTCDate") << QString("setUTCDate");
1285 QTest::newRow(dataTag: "Date.prototype.setMonth") << QString("Date.prototype.setMonth") << QString("setMonth");
1286 QTest::newRow(dataTag: "Date.prototype.setUTCMonth") << QString("Date.prototype.setUTCMonth") << QString("setUTCMonth");
1287 QTest::newRow(dataTag: "Date.prototype.setYear") << QString("Date.prototype.setYear") << QString("setYear");
1288 QTest::newRow(dataTag: "Date.prototype.setFullYear") << QString("Date.prototype.setFullYear") << QString("setFullYear");
1289 QTest::newRow(dataTag: "Date.prototype.setUTCFullYear") << QString("Date.prototype.setUTCFullYear") << QString("setUTCFullYear");
1290 QTest::newRow(dataTag: "Date.prototype.toUTCString") << QString("Date.prototype.toUTCString") << QString("toUTCString");
1291 QTest::newRow(dataTag: "Date.prototype.toGMTString") << QString("Date.prototype.toGMTString") << QString("toUTCString"); // yes, this is per spec
1292
1293 QTest::newRow(dataTag: "Error") << QString("Error") << QString("Error");
1294// QTest::newRow("Error.prototype.backtrace") << QString("Error.prototype.backtrace") << QString("backtrace");
1295 QTest::newRow(dataTag: "Error.prototype.toString") << QString("Error.prototype.toString") << QString("toString");
1296
1297 QTest::newRow(dataTag: "EvalError") << QString("EvalError") << QString("EvalError");
1298 QTest::newRow(dataTag: "RangeError") << QString("RangeError") << QString("RangeError");
1299 QTest::newRow(dataTag: "ReferenceError") << QString("ReferenceError") << QString("ReferenceError");
1300 QTest::newRow(dataTag: "SyntaxError") << QString("SyntaxError") << QString("SyntaxError");
1301 QTest::newRow(dataTag: "TypeError") << QString("TypeError") << QString("TypeError");
1302 QTest::newRow(dataTag: "URIError") << QString("URIError") << QString("URIError");
1303
1304 QTest::newRow(dataTag: "Function") << QString("Function") << QString("Function");
1305 QTest::newRow(dataTag: "Function.prototype.toString") << QString("Function.prototype.toString") << QString("toString");
1306 QTest::newRow(dataTag: "Function.prototype.apply") << QString("Function.prototype.apply") << QString("apply");
1307 QTest::newRow(dataTag: "Function.prototype.call") << QString("Function.prototype.call") << QString("call");
1308/* In V8, those function are only there for signals
1309 QTest::newRow("Function.prototype.connect") << QString("Function.prototype.connect") << QString("connect");
1310 QTest::newRow("Function.prototype.disconnect") << QString("Function.prototype.disconnect") << QString("disconnect");*/
1311
1312 QTest::newRow(dataTag: "Math.abs") << QString("Math.abs") << QString("abs");
1313 QTest::newRow(dataTag: "Math.acos") << QString("Math.acos") << QString("acos");
1314 QTest::newRow(dataTag: "Math.asin") << QString("Math.asin") << QString("asin");
1315 QTest::newRow(dataTag: "Math.atan") << QString("Math.atan") << QString("atan");
1316 QTest::newRow(dataTag: "Math.atan2") << QString("Math.atan2") << QString("atan2");
1317 QTest::newRow(dataTag: "Math.ceil") << QString("Math.ceil") << QString("ceil");
1318 QTest::newRow(dataTag: "Math.cos") << QString("Math.cos") << QString("cos");
1319 QTest::newRow(dataTag: "Math.exp") << QString("Math.exp") << QString("exp");
1320 QTest::newRow(dataTag: "Math.floor") << QString("Math.floor") << QString("floor");
1321 QTest::newRow(dataTag: "Math.log") << QString("Math.log") << QString("log");
1322 QTest::newRow(dataTag: "Math.max") << QString("Math.max") << QString("max");
1323 QTest::newRow(dataTag: "Math.min") << QString("Math.min") << QString("min");
1324 QTest::newRow(dataTag: "Math.pow") << QString("Math.pow") << QString("pow");
1325 QTest::newRow(dataTag: "Math.random") << QString("Math.random") << QString("random");
1326 QTest::newRow(dataTag: "Math.round") << QString("Math.round") << QString("round");
1327 QTest::newRow(dataTag: "Math.sign") << QString("Math.sign") << QString("sign");
1328 QTest::newRow(dataTag: "Math.sin") << QString("Math.sin") << QString("sin");
1329 QTest::newRow(dataTag: "Math.sqrt") << QString("Math.sqrt") << QString("sqrt");
1330 QTest::newRow(dataTag: "Math.tan") << QString("Math.tan") << QString("tan");
1331
1332 QTest::newRow(dataTag: "Number") << QString("Number") << QString("Number");
1333 QTest::newRow(dataTag: "Number.isFinite") << QString("Number.isFinite") << QString("isFinite");
1334 QTest::newRow(dataTag: "Number.isNaN") << QString("Number.isNaN") << QString("isNaN");
1335 QTest::newRow(dataTag: "Number.prototype.toString") << QString("Number.prototype.toString") << QString("toString");
1336 QTest::newRow(dataTag: "Number.prototype.toLocaleString") << QString("Number.prototype.toLocaleString") << QString("toLocaleString");
1337 QTest::newRow(dataTag: "Number.prototype.valueOf") << QString("Number.prototype.valueOf") << QString("valueOf");
1338 QTest::newRow(dataTag: "Number.prototype.toFixed") << QString("Number.prototype.toFixed") << QString("toFixed");
1339 QTest::newRow(dataTag: "Number.prototype.toExponential") << QString("Number.prototype.toExponential") << QString("toExponential");
1340 QTest::newRow(dataTag: "Number.prototype.toPrecision") << QString("Number.prototype.toPrecision") << QString("toPrecision");
1341
1342 QTest::newRow(dataTag: "Object") << QString("Object") << QString("Object");
1343 QTest::newRow(dataTag: "Object.prototype.toString") << QString("Object.prototype.toString") << QString("toString");
1344 QTest::newRow(dataTag: "Object.prototype.toLocaleString") << QString("Object.prototype.toLocaleString") << QString("toLocaleString");
1345 QTest::newRow(dataTag: "Object.prototype.valueOf") << QString("Object.prototype.valueOf") << QString("valueOf");
1346 QTest::newRow(dataTag: "Object.prototype.hasOwnProperty") << QString("Object.prototype.hasOwnProperty") << QString("hasOwnProperty");
1347 QTest::newRow(dataTag: "Object.prototype.isPrototypeOf") << QString("Object.prototype.isPrototypeOf") << QString("isPrototypeOf");
1348 QTest::newRow(dataTag: "Object.prototype.propertyIsEnumerable") << QString("Object.prototype.propertyIsEnumerable") << QString("propertyIsEnumerable");
1349 QTest::newRow(dataTag: "Object.prototype.__defineGetter__") << QString("Object.prototype.__defineGetter__") << QString("__defineGetter__");
1350 QTest::newRow(dataTag: "Object.prototype.__defineSetter__") << QString("Object.prototype.__defineSetter__") << QString("__defineSetter__");
1351
1352 QTest::newRow(dataTag: "RegExp") << QString("RegExp") << QString("RegExp");
1353 QTest::newRow(dataTag: "RegExp.prototype.exec") << QString("RegExp.prototype.exec") << QString("exec");
1354 QTest::newRow(dataTag: "RegExp.prototype.test") << QString("RegExp.prototype.test") << QString("test");
1355 QTest::newRow(dataTag: "RegExp.prototype.toString") << QString("RegExp.prototype.toString") << QString("toString");
1356
1357 QTest::newRow(dataTag: "String") << QString("String") << QString("String");
1358 QTest::newRow(dataTag: "String.prototype.toString") << QString("String.prototype.toString") << QString("toString");
1359 QTest::newRow(dataTag: "String.prototype.valueOf") << QString("String.prototype.valueOf") << QString("valueOf");
1360 QTest::newRow(dataTag: "String.prototype.charAt") << QString("String.prototype.charAt") << QString("charAt");
1361 QTest::newRow(dataTag: "String.prototype.charCodeAt") << QString("String.prototype.charCodeAt") << QString("charCodeAt");
1362 QTest::newRow(dataTag: "String.prototype.concat") << QString("String.prototype.concat") << QString("concat");
1363 QTest::newRow(dataTag: "String.prototype.endsWith") << QString("String.prototype.endsWith") << QString("endsWith");
1364 QTest::newRow(dataTag: "String.prototype.includes") << QString("String.prototype.includes") << QString("includes");
1365 QTest::newRow(dataTag: "String.prototype.indexOf") << QString("String.prototype.indexOf") << QString("indexOf");
1366 QTest::newRow(dataTag: "String.prototype.lastIndexOf") << QString("String.prototype.lastIndexOf") << QString("lastIndexOf");
1367 QTest::newRow(dataTag: "String.prototype.localeCompare") << QString("String.prototype.localeCompare") << QString("localeCompare");
1368 QTest::newRow(dataTag: "String.prototype.match") << QString("String.prototype.match") << QString("match");
1369 QTest::newRow(dataTag: "String.prototype.repeat") << QString("String.prototype.repeat") << QString("repeat");
1370 QTest::newRow(dataTag: "String.prototype.replace") << QString("String.prototype.replace") << QString("replace");
1371 QTest::newRow(dataTag: "String.prototype.search") << QString("String.prototype.search") << QString("search");
1372 QTest::newRow(dataTag: "String.prototype.slice") << QString("String.prototype.slice") << QString("slice");
1373 QTest::newRow(dataTag: "String.prototype.split") << QString("String.prototype.split") << QString("split");
1374 QTest::newRow(dataTag: "String.prototype.startsWith") << QString("String.prototype.startsWith") << QString("startsWith");
1375 QTest::newRow(dataTag: "String.prototype.substring") << QString("String.prototype.substring") << QString("substring");
1376 QTest::newRow(dataTag: "String.prototype.toLowerCase") << QString("String.prototype.toLowerCase") << QString("toLowerCase");
1377 QTest::newRow(dataTag: "String.prototype.toLocaleLowerCase") << QString("String.prototype.toLocaleLowerCase") << QString("toLocaleLowerCase");
1378 QTest::newRow(dataTag: "String.prototype.toUpperCase") << QString("String.prototype.toUpperCase") << QString("toUpperCase");
1379 QTest::newRow(dataTag: "String.prototype.toLocaleUpperCase") << QString("String.prototype.toLocaleUpperCase") << QString("toLocaleUpperCase");
1380}
1381
1382void tst_QJSEngine::builtinFunctionNames()
1383{
1384 QFETCH(QString, expression);
1385 QFETCH(QString, expectedName);
1386 QJSEngine eng;
1387 // The "name" property is actually non-standard, but JSC supports it.
1388 QJSValue ret = eng.evaluate(program: QString::fromLatin1(str: "%0.name").arg(a: expression));
1389 QVERIFY(ret.isString());
1390 QCOMPARE(ret.toString(), expectedName);
1391}
1392
1393void tst_QJSEngine::evaluate_data()
1394{
1395 QTest::addColumn<QString>(name: "code");
1396 QTest::addColumn<int>(name: "lineNumber");
1397 QTest::addColumn<bool>(name: "expectHadError");
1398 QTest::addColumn<int>(name: "expectErrorLineNumber");
1399
1400 QTest::newRow(dataTag: "(newline)") << QString("\n") << -1 << false << -1;
1401 QTest::newRow(dataTag: "0 //") << QString("0 //") << -1 << false << -1;
1402 QTest::newRow(dataTag: "/* */") << QString("/* */") << -1 << false << -1;
1403 QTest::newRow(dataTag: "//") << QString("//") << -1 << false << -1;
1404 QTest::newRow(dataTag: "(spaces)") << QString(" ") << -1 << false << -1;
1405 QTest::newRow(dataTag: "(empty)") << QString("") << -1 << false << -1;
1406 QTest::newRow(dataTag: "0") << QString("0") << -1 << false << -1;
1407 QTest::newRow(dataTag: "0=1") << QString("\n0=1;\n") << -1 << true << 2;
1408 QTest::newRow(dataTag: "a=1") << QString("a=1\n") << -1 << false << -1;
1409 QTest::newRow(dataTag: "a=1;K") << QString("a=1;\nK") << -1 << true << 2;
1410
1411 QTest::newRow(dataTag: "f()") << QString("function f()\n"
1412 "{\n"
1413 " var a;\n"
1414 " var b=\";\n" // here's the error
1415 "}\n"
1416 "f();\n")
1417 << -1 << true << 4;
1418
1419 QTest::newRow(dataTag: "0") << QString("0") << 10 << false << -1;
1420 QTest::newRow(dataTag: "0=1") << QString("\n\n0=1\n") << 10 << true << 12;
1421 QTest::newRow(dataTag: "a=1") << QString("a=1\n") << 10 << false << -1;
1422 QTest::newRow(dataTag: "a=1;K") << QString("a=1;\n\nK") << 10 << true << 12;
1423
1424 QTest::newRow(dataTag: "f()") << QString("function f()\n"
1425 "{\n"
1426 " var a;\n"
1427 "\n\n"
1428 " var b=\";\n" // here's the error
1429 "}\n"
1430 "f();\n")
1431 << 10 << true << 15;
1432 QTest::newRow(dataTag: "functionThatDoesntExist()")
1433 << QString(";\n;\n;\nfunctionThatDoesntExist()")
1434 << -1 << true << 4;
1435 QTest::newRow(dataTag: "for (var p in this) { continue labelThatDoesntExist; }")
1436 << QString("for (var p in this) {\ncontinue labelThatDoesntExist; }")
1437 << 4 << true << 5;
1438 QTest::newRow(dataTag: "duplicateLabel: { duplicateLabel: ; }")
1439 << QString("duplicateLabel: { duplicateLabel: ; }")
1440 << 12 << true << 12;
1441
1442 QTest::newRow(dataTag: "/=/") << QString("/=/") << -1 << false << -1;
1443 QTest::newRow(dataTag: "/=/g") << QString("/=/g") << -1 << false << -1;
1444 QTest::newRow(dataTag: "/a/") << QString("/a/") << -1 << false << -1;
1445 QTest::newRow(dataTag: "/a/g") << QString("/a/g") << -1 << false << -1;
1446 QTest::newRow(dataTag: "/a/gim") << QString("/a/gim") << -1 << false << -1;
1447 QTest::newRow(dataTag: "/a/gimp") << QString("/a/gimp") << 1 << true << 1;
1448 QTest::newRow(dataTag: "empty-array-concat") << QString("var a = []; var b = [1]; var c = a.concat(b); ") << 1 << false << -1;
1449 QTest::newRow(dataTag: "object-literal") << QString("var a = {\"0\":\"#\",\"2\":\"#\",\"5\":\"#\",\"8\":\"#\",\"6\":\"#\",\"12\":\"#\",\"13\":\"#\",\"16\":\"#\",\"18\":\"#\",\"39\":\"#\",\"40\":\"#\"}") << 1 << false << -1;
1450}
1451
1452void tst_QJSEngine::evaluate()
1453{
1454 QFETCH(QString, code);
1455 QFETCH(int, lineNumber);
1456 QFETCH(bool, expectHadError);
1457 QFETCH(int, expectErrorLineNumber);
1458
1459 QJSEngine eng;
1460 QJSValue ret;
1461 if (lineNumber != -1)
1462 ret = eng.evaluate(program: code, /*fileName =*/QString(), lineNumber);
1463 else
1464 ret = eng.evaluate(program: code);
1465 QCOMPARE(ret.isError(), expectHadError);
1466 if (ret.isError()) {
1467 QVERIFY(ret.property("lineNumber").strictlyEquals(eng.toScriptValue(expectErrorLineNumber)));
1468 }
1469}
1470
1471void tst_QJSEngine::errorMessage_QT679()
1472{
1473 QJSEngine engine;
1474 engine.globalObject().setProperty(name: "foo", value: 15);
1475 QJSValue error = engine.evaluate(program: "'hello world';\nfoo.bar.blah");
1476 QVERIFY(error.isError());
1477 QVERIFY(error.toString().startsWith(QString::fromLatin1("TypeError: ")));
1478}
1479
1480struct Foo {
1481public:
1482 int x = -1, y = -1;
1483 Foo() {}
1484};
1485
1486Q_DECLARE_METATYPE(Foo)
1487Q_DECLARE_METATYPE(Foo*)
1488
1489Q_DECLARE_METATYPE(QList<Foo>)
1490Q_DECLARE_METATYPE(QVector<QChar>)
1491Q_DECLARE_METATYPE(QStack<int>)
1492Q_DECLARE_METATYPE(QQueue<char>)
1493
1494void tst_QJSEngine::valueConversion_basic()
1495{
1496 QJSEngine eng;
1497 {
1498 QJSValue num = eng.toScriptValue(value: 123);
1499 QCOMPARE(num.isNumber(), true);
1500 QCOMPARE(num.strictlyEquals(eng.toScriptValue(123)), true);
1501
1502 int inum = eng.fromScriptValue<int>(value: num);
1503 QCOMPARE(inum, 123);
1504
1505 QString snum = eng.fromScriptValue<QString>(value: num);
1506 QCOMPARE(snum, QLatin1String("123"));
1507 }
1508 {
1509 QJSValue num = eng.toScriptValue(value: 123);
1510 QCOMPARE(num.isNumber(), true);
1511 QCOMPARE(num.strictlyEquals(eng.toScriptValue(123)), true);
1512
1513 int inum = eng.fromScriptValue<int>(value: num);
1514 QCOMPARE(inum, 123);
1515
1516 QString snum = eng.fromScriptValue<QString>(value: num);
1517 QCOMPARE(snum, QLatin1String("123"));
1518 }
1519 {
1520 QJSValue num = eng.toScriptValue(value: 123);
1521 QCOMPARE(eng.fromScriptValue<char>(num), char(123));
1522 QCOMPARE(eng.fromScriptValue<unsigned char>(num), (unsigned char)(123));
1523 QCOMPARE(eng.fromScriptValue<short>(num), short(123));
1524 QCOMPARE(eng.fromScriptValue<unsigned short>(num), (unsigned short)(123));
1525 QCOMPARE(eng.fromScriptValue<float>(num), float(123));
1526 QCOMPARE(eng.fromScriptValue<double>(num), double(123));
1527 QCOMPARE(eng.fromScriptValue<qlonglong>(num), qlonglong(123));
1528 QCOMPARE(eng.fromScriptValue<qulonglong>(num), qulonglong(123));
1529 }
1530 {
1531 QJSValue num(123);
1532 QCOMPARE(eng.fromScriptValue<char>(num), char(123));
1533 QCOMPARE(eng.fromScriptValue<unsigned char>(num), (unsigned char)(123));
1534 QCOMPARE(eng.fromScriptValue<short>(num), short(123));
1535 QCOMPARE(eng.fromScriptValue<unsigned short>(num), (unsigned short)(123));
1536 QCOMPARE(eng.fromScriptValue<float>(num), float(123));
1537 QCOMPARE(eng.fromScriptValue<double>(num), double(123));
1538 QCOMPARE(eng.fromScriptValue<qlonglong>(num), qlonglong(123));
1539 QCOMPARE(eng.fromScriptValue<qulonglong>(num), qulonglong(123));
1540 }
1541
1542 {
1543 QJSValue num = eng.toScriptValue(Q_INT64_C(0x100000000));
1544 QCOMPARE(eng.fromScriptValue<qlonglong>(num), Q_INT64_C(0x100000000));
1545 QCOMPARE(eng.fromScriptValue<qulonglong>(num), Q_UINT64_C(0x100000000));
1546 }
1547
1548 {
1549 QChar c = QLatin1Char('c');
1550 QJSValue str = eng.toScriptValue(value: QString::fromLatin1(str: "ciao"));
1551 QCOMPARE(eng.fromScriptValue<QChar>(str), c);
1552 QJSValue code = eng.toScriptValue(value: c.unicode());
1553 QCOMPARE(eng.fromScriptValue<QChar>(code), c);
1554 QCOMPARE(eng.fromScriptValue<QChar>(eng.toScriptValue(c)), c);
1555 }
1556
1557 QVERIFY(eng.toScriptValue(static_cast<void *>(nullptr)).isNull());
1558}
1559
1560void tst_QJSEngine::valueConversion_QVariant()
1561{
1562 QJSEngine eng;
1563 // qScriptValueFromValue() should be "smart" when the argument is a QVariant
1564 {
1565 QJSValue val = eng.toScriptValue(value: QVariant());
1566 QVERIFY(!val.isVariant());
1567 QVERIFY(val.isUndefined());
1568 }
1569 // Checking nested QVariants
1570 {
1571 QVariant tmp1;
1572 QVariant tmp2(QMetaType::QVariant, &tmp1);
1573 QCOMPARE(QMetaType::Type(tmp2.userType()), QMetaType::QVariant);
1574
1575 QJSValue val1 = eng.toScriptValue(value: tmp1);
1576 QJSValue val2 = eng.toScriptValue(value: tmp2);
1577 QVERIFY(val1.isUndefined());
1578 QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
1579 QVERIFY(!val2.isUndefined());
1580 QVERIFY(!val1.isVariant());
1581 QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
1582 QVERIFY(val2.isVariant());
1583 }
1584 {
1585 QVariant tmp1(123);
1586 QVariant tmp2(QMetaType::QVariant, &tmp1);
1587 QVariant tmp3(QMetaType::QVariant, &tmp2);
1588 QCOMPARE(QMetaType::Type(tmp1.userType()), QMetaType::Int);
1589 QCOMPARE(QMetaType::Type(tmp2.userType()), QMetaType::QVariant);
1590 QCOMPARE(QMetaType::Type(tmp3.userType()), QMetaType::QVariant);
1591
1592 QJSValue val1 = eng.toScriptValue(value: tmp2);
1593 QJSValue val2 = eng.toScriptValue(value: tmp3);
1594 QVERIFY(!val1.isUndefined());
1595 QVERIFY(!val2.isUndefined());
1596 QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
1597 QVERIFY(val1.isVariant());
1598 QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
1599 QVERIFY(val2.isVariant());
1600 QCOMPARE(val1.toVariant().toInt(), 123);
1601 QCOMPARE(eng.toScriptValue(val2.toVariant()).toVariant().toInt(), 123);
1602 }
1603 {
1604 QJSValue val = eng.toScriptValue(value: QVariant(true));
1605 QVERIFY(!val.isVariant());
1606 QVERIFY(val.isBool());
1607 QCOMPARE(val.toBool(), true);
1608 }
1609 {
1610 QJSValue val = eng.toScriptValue(value: QVariant(int(123)));
1611 QVERIFY(!val.isVariant());
1612 QVERIFY(val.isNumber());
1613 QCOMPARE(val.toNumber(), qreal(123));
1614 }
1615 {
1616 QJSValue val = eng.toScriptValue(value: QVariant(qreal(1.25)));
1617 QVERIFY(!val.isVariant());
1618 QVERIFY(val.isNumber());
1619 QCOMPARE(val.toNumber(), qreal(1.25));
1620 }
1621 {
1622 QString str = QString::fromLatin1(str: "ciao");
1623 QJSValue val = eng.toScriptValue(value: QVariant(str));
1624 QVERIFY(!val.isVariant());
1625 QVERIFY(val.isString());
1626 QCOMPARE(val.toString(), str);
1627 }
1628 {
1629 QJSValue val = eng.toScriptValue(value: QVariant::fromValue(value: (QObject*)this));
1630 QVERIFY(!val.isVariant());
1631 QVERIFY(val.isQObject());
1632 QCOMPARE(val.toQObject(), (QObject*)this);
1633 }
1634 {
1635 QVariant var = QVariant::fromValue(value: QPoint(123, 456));
1636 QJSValue val = eng.toScriptValue(value: var);
1637 QVERIFY(!val.isVariant());
1638 QCOMPARE(val.toVariant(), var);
1639 }
1640
1641 QCOMPARE(qjsvalue_cast<QVariant>(QJSValue(123)), QVariant(123));
1642
1643 QVERIFY(eng.toScriptValue(QVariant(QMetaType::VoidStar, nullptr)).isNull());
1644 QVERIFY(eng.toScriptValue(QVariant::fromValue(nullptr)).isNull());
1645
1646 {
1647 QVariantMap map;
1648 map.insert(key: "42", value: "the answer to life the universe and everything");
1649 QJSValue val = eng.toScriptValue(value: map);
1650 QVERIFY(val.isObject());
1651 QCOMPARE(val.property(42).toString(), map.value(QStringLiteral("42")).toString());
1652 }
1653}
1654
1655void tst_QJSEngine::valueConversion_basic2()
1656{
1657 QJSEngine eng;
1658 // more built-in types
1659 {
1660 QJSValue val = eng.toScriptValue(value: uint(123));
1661 QVERIFY(val.isNumber());
1662 QCOMPARE(val.toInt(), 123);
1663 }
1664 {
1665 QJSValue val = eng.toScriptValue(value: qulonglong(123));
1666 QVERIFY(val.isNumber());
1667 QCOMPARE(val.toInt(), 123);
1668 }
1669 {
1670 QJSValue val = eng.toScriptValue(value: float(123));
1671 QVERIFY(val.isNumber());
1672 QCOMPARE(val.toInt(), 123);
1673 }
1674 {
1675 QJSValue val = eng.toScriptValue(value: short(123));
1676 QVERIFY(val.isNumber());
1677 QCOMPARE(val.toInt(), 123);
1678 }
1679 {
1680 QJSValue val = eng.toScriptValue(value: ushort(123));
1681 QVERIFY(val.isNumber());
1682 QCOMPARE(val.toInt(), 123);
1683 }
1684 {
1685 QJSValue val = eng.toScriptValue(value: char(123));
1686 QVERIFY(val.isNumber());
1687 QCOMPARE(val.toInt(), 123);
1688 }
1689 {
1690 QJSValue val = eng.toScriptValue(value: uchar(123));
1691 QVERIFY(val.isNumber());
1692 QCOMPARE(val.toInt(), 123);
1693 }
1694}
1695
1696void tst_QJSEngine::valueConversion_dateTime()
1697{
1698 QJSEngine eng;
1699 {
1700 QDateTime in = QDateTime::currentDateTime();
1701 QJSValue val = eng.toScriptValue(value: in);
1702 QVERIFY(val.isDate());
1703 QCOMPARE(val.toDateTime(), in);
1704 }
1705 {
1706 QDate in = QDate::currentDate();
1707 QJSValue val = eng.toScriptValue(value: in);
1708 QVERIFY(val.isDate());
1709 QCOMPARE(val.toDateTime().date(), in);
1710 }
1711}
1712
1713void tst_QJSEngine::valueConversion_regExp()
1714{
1715 QJSEngine eng;
1716 {
1717 QRegExp in = QRegExp("foo");
1718 QJSValue val = eng.toScriptValue(value: in);
1719 QVERIFY(val.isRegExp());
1720 QRegExp out = qjsvalue_cast<QRegExp>(value: val);
1721 QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::patternSyntax (always uses RegExp2)", Continue);
1722 QCOMPARE(out.patternSyntax(), in.patternSyntax());
1723 QCOMPARE(out.pattern(), in.pattern());
1724 QCOMPARE(out.caseSensitivity(), in.caseSensitivity());
1725 QCOMPARE(out.isMinimal(), in.isMinimal());
1726 }
1727 {
1728 QRegExp in = QRegExp("foo", Qt::CaseSensitive, QRegExp::RegExp2);
1729 QJSValue val = eng.toScriptValue(value: in);
1730 QVERIFY(val.isRegExp());
1731 QCOMPARE(qjsvalue_cast<QRegExp>(val), in);
1732 }
1733 {
1734 QRegExp in = QRegExp("foo");
1735 in.setMinimal(true);
1736 QJSValue val = eng.toScriptValue(value: in);
1737 QVERIFY(val.isRegExp());
1738 QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::minimal (always false)", Continue);
1739 QCOMPARE(qjsvalue_cast<QRegExp>(val).isMinimal(), in.isMinimal());
1740 }
1741}
1742
1743void tst_QJSEngine::valueConversion_RegularExpression()
1744{
1745 QJSEngine eng;
1746 {
1747 QRegularExpression in = QRegularExpression("foo");
1748 QJSValue val = eng.toScriptValue(value: in);
1749 QVERIFY(val.isRegExp());
1750 QRegularExpression out = qjsvalue_cast<QRegularExpression>(value: val);
1751 QCOMPARE(out.pattern(), in.pattern());
1752 QCOMPARE(out.patternOptions(), in.patternOptions());
1753 }
1754 {
1755 QRegularExpression in = QRegularExpression("foo",
1756 QRegularExpression::CaseInsensitiveOption);
1757 QJSValue val = eng.toScriptValue(value: in);
1758 QVERIFY(val.isRegExp());
1759 QCOMPARE(qjsvalue_cast<QRegularExpression>(val), in);
1760 QRegularExpression out = qjsvalue_cast<QRegularExpression>(value: val);
1761 QCOMPARE(out.patternOptions(), in.patternOptions());
1762 }
1763}
1764
1765Q_DECLARE_METATYPE(QGradient)
1766Q_DECLARE_METATYPE(QGradient*)
1767Q_DECLARE_METATYPE(QLinearGradient)
1768
1769class Klazz : public QWidget,
1770 public QStandardItem,
1771 public QGraphicsItem
1772{
1773 Q_INTERFACES(QGraphicsItem)
1774 Q_OBJECT
1775public:
1776 Klazz(QWidget *parent = nullptr) : QWidget(parent) { }
1777 virtual QRectF boundingRect() const { return QRectF(); }
1778 virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { }
1779};
1780
1781Q_DECLARE_METATYPE(Klazz*)
1782Q_DECLARE_METATYPE(QStandardItem*)
1783
1784void tst_QJSEngine::castWithMultipleInheritance()
1785{
1786 QJSEngine eng;
1787 Klazz klz;
1788 QJSValue v = eng.newQObject(object: &klz);
1789
1790 QCOMPARE(qjsvalue_cast<Klazz*>(v), &klz);
1791 QCOMPARE(qjsvalue_cast<QWidget*>(v), (QWidget *)&klz);
1792 QCOMPARE(qjsvalue_cast<QObject*>(v), (QObject *)&klz);
1793 QCOMPARE(qjsvalue_cast<QStandardItem*>(v), (QStandardItem *)&klz);
1794 QCOMPARE(qjsvalue_cast<QGraphicsItem*>(v), (QGraphicsItem *)&klz);
1795}
1796
1797void tst_QJSEngine::collectGarbage()
1798{
1799 QJSEngine eng;
1800 eng.evaluate(program: "a = new Object(); a = new Object(); a = new Object()");
1801 QJSValue a = eng.newObject();
1802 a = eng.newObject();
1803 a = eng.newObject();
1804 QPointer<QObject> ptr = new QObject();
1805 QVERIFY(ptr != nullptr);
1806 (void)eng.newQObject(object: ptr);
1807 eng.collectGarbage();
1808 if (ptr)
1809 QGuiApplication::sendPostedEvents(receiver: ptr, event_type: QEvent::DeferredDelete);
1810 QVERIFY(ptr.isNull());
1811}
1812
1813class TestObjectContainer : public QObject
1814{
1815 Q_OBJECT
1816 Q_PROPERTY(QObject *dummy MEMBER m_dummy CONSTANT)
1817
1818public:
1819 TestObjectContainer() : m_dummy(new QObject(this)) {}
1820
1821private:
1822 QObject *m_dummy;
1823};
1824
1825void tst_QJSEngine::collectGarbageNestedWrappersTwoEngines()
1826{
1827 QJSEngine engine1;
1828 QJSEngine engine2;
1829
1830 TestObjectContainer container;
1831 QQmlEngine::setObjectOwnership(&container, QQmlEngine::CppOwnership);
1832
1833 engine1.globalObject().setProperty(name: "foobar", value: engine1.newQObject(object: &container));
1834 engine2.globalObject().setProperty(name: "foobar", value: engine2.newQObject(object: &container));
1835
1836 engine1.evaluate(program: "foobar.dummy.baz = 42");
1837 engine2.evaluate(program: "foobar.dummy.baz = 43");
1838
1839 QCOMPARE(engine1.evaluate("foobar.dummy.baz").toInt(), 42);
1840 QCOMPARE(engine2.evaluate("foobar.dummy.baz").toInt(), 43);
1841
1842 engine1.collectGarbage();
1843 engine2.collectGarbage();
1844
1845 // The GC should not collect dummy object wrappers neither in engine1 nor engine2, we
1846 // verify that by checking whether the baz property still has its previous value.
1847 QCOMPARE(engine1.evaluate("foobar.dummy.baz").toInt(), 42);
1848 QCOMPARE(engine2.evaluate("foobar.dummy.baz").toInt(), 43);
1849}
1850
1851void tst_QJSEngine::gcWithNestedDataStructure()
1852{
1853 // The GC must be able to traverse deeply nested objects, otherwise this
1854 // test would crash.
1855 QJSEngine eng;
1856 eng.installExtensions(extensions: QJSEngine::GarbageCollectionExtension);
1857
1858 QJSValue ret = eng.evaluate(
1859 program: "function makeList(size)"
1860 "{"
1861 " var head = { };"
1862 " var l = head;"
1863 " for (var i = 0; i < size; ++i) {"
1864 " l.data = i + \"\";"
1865 " l.next = { }; l = l.next;"
1866 " }"
1867 " l.next = null;"
1868 " return head;"
1869 "}");
1870 QVERIFY(!ret.isError());
1871 const int size = 200;
1872 QJSValue head = eng.evaluate(program: QString::fromLatin1(str: "makeList(%0)").arg(a: size));
1873 QVERIFY(!head.isError());
1874 for (int x = 0; x < 2; ++x) {
1875 if (x == 1)
1876 eng.evaluate(program: "gc()");
1877 QJSValue l = head;
1878 // Make sure all the nodes are still alive.
1879 for (int i = 0; i < 200; ++i) {
1880 QCOMPARE(l.property("data").toString(), QString::number(i));
1881 l = l.property(name: "next");
1882 }
1883 }
1884}
1885
1886void tst_QJSEngine::stacktrace()
1887{
1888 QString script = QString::fromLatin1(
1889 str: "function foo(counter) {\n"
1890 " switch (counter) {\n"
1891 " case 0: foo(counter+1); break;\n"
1892 " case 1: foo(counter+1); break;\n"
1893 " case 2: foo(counter+1); break;\n"
1894 " case 3: foo(counter+1); break;\n"
1895 " case 4: foo(counter+1); break;\n"
1896 " default:\n"
1897 " throw new Error('blah');\n"
1898 " }\n"
1899 "}\n"
1900 "foo(0);");
1901
1902 const QString fileName("testfile");
1903
1904 QStringList backtrace;
1905 backtrace << "foo(5)@testfile:9"
1906 << "foo(4)@testfile:7"
1907 << "foo(3)@testfile:6"
1908 << "foo(2)@testfile:5"
1909 << "foo(1)@testfile:4"
1910 << "foo(0)@testfile:3"
1911 << "<global>()@testfile:12";
1912
1913 QJSEngine eng;
1914 QJSValue result = eng.evaluate(program: script, fileName);
1915 QVERIFY(result.isError());
1916
1917 QJSValue stack = result.property(name: "stack");
1918
1919 QJSValueIterator it(stack);
1920 int counter = 5;
1921 while (it.hasNext()) {
1922 it.next();
1923 QJSValue obj = it.value();
1924 QJSValue frame = obj.property(name: "frame");
1925
1926 QCOMPARE(obj.property("fileName").toString(), fileName);
1927 if (counter >= 0) {
1928 QJSValue callee = frame.property(name: "arguments").property(name: "callee");
1929 QVERIFY(callee.strictlyEquals(eng.globalObject().property("foo")));
1930 QCOMPARE(obj.property("functionName").toString(), QString("foo"));
1931 int line = obj.property(name: "lineNumber").toInt();
1932 if (counter == 5)
1933 QCOMPARE(line, 9);
1934 else
1935 QCOMPARE(line, 3 + counter);
1936 } else {
1937 QVERIFY(frame.strictlyEquals(eng.globalObject()));
1938 QVERIFY(obj.property("functionName").toString().isEmpty());
1939 }
1940
1941 --counter;
1942 }
1943
1944 // throw something that isn't an Error object
1945 // ###FIXME: No uncaughtExceptionBacktrace: QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
1946 QString script2 = QString::fromLatin1(
1947 str: "function foo(counter) {\n"
1948 " switch (counter) {\n"
1949 " case 0: foo(counter+1); break;\n"
1950 " case 1: foo(counter+1); break;\n"
1951 " case 2: foo(counter+1); break;\n"
1952 " case 3: foo(counter+1); break;\n"
1953 " case 4: foo(counter+1); break;\n"
1954 " default:\n"
1955 " throw 'just a string';\n"
1956 " }\n"
1957 "}\n"
1958 "foo(0);");
1959
1960 QJSValue result2 = eng.evaluate(program: script2, fileName);
1961 QVERIFY(!result2.isError());
1962 QVERIFY(result2.isString());
1963
1964 {
1965 QString script3 = QString::fromLatin1(
1966 str: "'use strict'\n"
1967 "function throwUp() { throw new Error('up') }\n"
1968 "function indirectlyThrow() { return throwUp() }\n"
1969 "indirectlyThrow()\n"
1970 );
1971 QJSValue result3 = eng.evaluate(program: script3);
1972 QVERIFY(result3.isError());
1973 QJSValue stack = result3.property(name: "stack");
1974 QVERIFY(stack.isString());
1975 QString stackTrace = stack.toString();
1976 QVERIFY(!stackTrace.contains(QStringLiteral("indirectlyThrow")));
1977 QVERIFY(stackTrace.contains(QStringLiteral("elide")));
1978 }
1979}
1980
1981void tst_QJSEngine::numberParsing_data()
1982{
1983 QTest::addColumn<QString>(name: "string");
1984 QTest::addColumn<qreal>(name: "expect");
1985
1986 QTest::newRow(dataTag: "decimal 0") << QString("0") << qreal(0);
1987// QTest::newRow("octal 0") << QString("00") << qreal(00);
1988 QTest::newRow(dataTag: "hex 0") << QString("0x0") << qreal(0x0);
1989 QTest::newRow(dataTag: "decimal 100") << QString("100") << qreal(100);
1990 QTest::newRow(dataTag: "hex 100") << QString("0x100") << qreal(0x100);
1991// QTest::newRow("octal 100") << QString("0100") << qreal(0100);
1992 QTest::newRow(dataTag: "decimal 4G") << QString("4294967296") << qreal(Q_UINT64_C(4294967296));
1993 QTest::newRow(dataTag: "hex 4G") << QString("0x100000000") << qreal(Q_UINT64_C(0x100000000));
1994// QTest::newRow("octal 4G") << QString("040000000000") << qreal(Q_UINT64_C(040000000000));
1995 QTest::newRow(dataTag: "0.5") << QString("0.5") << qreal(0.5);
1996 QTest::newRow(dataTag: "1.5") << QString("1.5") << qreal(1.5);
1997 QTest::newRow(dataTag: "1e2") << QString("1e2") << qreal(100);
1998}
1999
2000void tst_QJSEngine::numberParsing()
2001{
2002 QFETCH(QString, string);
2003 QFETCH(qreal, expect);
2004
2005 QJSEngine eng;
2006 QJSValue ret = eng.evaluate(program: string);
2007 QVERIFY(ret.isNumber());
2008 qreal actual = ret.toNumber();
2009 QCOMPARE(actual, expect);
2010}
2011
2012// see ECMA-262, section 7.9
2013// This is testing ECMA compliance, not our C++ API, but it's important that
2014// the back-end is conformant in this regard.
2015void tst_QJSEngine::automaticSemicolonInsertion()
2016{
2017 QJSEngine eng;
2018 {
2019 QJSValue ret = eng.evaluate(program: "{ 1 2 } 3");
2020 QVERIFY(ret.isError());
2021 QVERIFY(ret.toString().contains("SyntaxError"));
2022 }
2023 {
2024 QJSValue ret = eng.evaluate(program: "{ 1\n2 } 3");
2025 QVERIFY(ret.isNumber());
2026 QCOMPARE(ret.toInt(), 3);
2027 }
2028 {
2029 QJSValue ret = eng.evaluate(program: "for (a; b\n)");
2030 QVERIFY(ret.isError());
2031 QVERIFY(ret.toString().contains("SyntaxError"));
2032 }
2033 {
2034 QJSValue ret = eng.evaluate(program: "(function() { return\n1 + 2 })()");
2035 QVERIFY(ret.isUndefined());
2036 }
2037 {
2038 eng.evaluate(program: "c = 2; b = 1");
2039 QJSValue ret = eng.evaluate(program: "a = b\n++c");
2040 QVERIFY(ret.isNumber());
2041 QCOMPARE(ret.toInt(), 3);
2042 }
2043 {
2044 QJSValue ret = eng.evaluate(program: "if (a > b)\nelse c = d");
2045 QVERIFY(ret.isError());
2046 QVERIFY(ret.toString().contains("SyntaxError"));
2047 }
2048 {
2049 eng.evaluate(program: "function c() { return { foo: function() { return 5; } } }");
2050 eng.evaluate(program: "b = 1; d = 2; e = 3");
2051 QJSValue ret = eng.evaluate(program: "a = b + c\n(d + e).foo()");
2052 QVERIFY(ret.isNumber());
2053 QCOMPARE(ret.toInt(), 6);
2054 }
2055 {
2056 QJSValue ret = eng.evaluate(program: "throw\n1");
2057 QVERIFY(ret.isError());
2058 QVERIFY(ret.toString().contains("SyntaxError"));
2059 }
2060 {
2061 QJSValue ret = eng.evaluate(program: "a = Number(1)\n++a");
2062 QVERIFY(ret.isNumber());
2063 QCOMPARE(ret.toInt(), 2);
2064 }
2065
2066 // "a semicolon is never inserted automatically if the semicolon
2067 // would then be parsed as an empty statement"
2068 {
2069 eng.evaluate(program: "a = 123");
2070 QJSValue ret = eng.evaluate(program: "if (0)\n ++a; a");
2071 QVERIFY(ret.isNumber());
2072 QCOMPARE(ret.toInt(), 123);
2073 }
2074 {
2075 eng.evaluate(program: "a = 123");
2076 QJSValue ret = eng.evaluate(program: "if (0)\n --a; a");
2077 QVERIFY(ret.isNumber());
2078 QCOMPARE(ret.toInt(), 123);
2079 }
2080 {
2081 eng.evaluate(program: "a = 123");
2082 QJSValue ret = eng.evaluate(program: "if ((0))\n ++a; a");
2083 QVERIFY(ret.isNumber());
2084 QCOMPARE(ret.toInt(), 123);
2085 }
2086 {
2087 eng.evaluate(program: "a = 123");
2088 QJSValue ret = eng.evaluate(program: "if ((0))\n --a; a");
2089 QVERIFY(ret.isNumber());
2090 QCOMPARE(ret.toInt(), 123);
2091 }
2092 {
2093 eng.evaluate(program: "a = 123");
2094 QJSValue ret = eng.evaluate(program: "if (0\n)\n ++a; a");
2095 QVERIFY(ret.isNumber());
2096 QCOMPARE(ret.toInt(), 123);
2097 }
2098 {
2099 eng.evaluate(program: "a = 123");
2100 QJSValue ret = eng.evaluate(program: "if (0\n ++a; a");
2101 QVERIFY(ret.isError());
2102 }
2103 {
2104 eng.evaluate(program: "a = 123");
2105 QJSValue ret = eng.evaluate(program: "if (0))\n ++a; a");
2106 QVERIFY(ret.isError());
2107 }
2108 {
2109 QJSValue ret = eng.evaluate(program: "n = 0; for (i = 0; i < 10; ++i)\n ++n; n");
2110 QVERIFY(ret.isNumber());
2111 QCOMPARE(ret.toInt(), 10);
2112 }
2113 {
2114 QJSValue ret = eng.evaluate(program: "n = 30; for (i = 0; i < 10; ++i)\n --n; n");
2115 QVERIFY(ret.isNumber());
2116 QCOMPARE(ret.toInt(), 20);
2117 }
2118 {
2119 QJSValue ret = eng.evaluate(program: "n = 0; for (var i = 0; i < 10; ++i)\n ++n; n");
2120 QVERIFY(ret.isNumber());
2121 QCOMPARE(ret.toInt(), 10);
2122 }
2123 {
2124 QJSValue ret = eng.evaluate(program: "n = 30; for (var i = 0; i < 10; ++i)\n --n; n");
2125 QVERIFY(ret.isNumber());
2126 QCOMPARE(ret.toInt(), 20);
2127 }
2128 {
2129 QJSValue ret = eng.evaluate(program: "n = 0; i = 0; while (i++ < 10)\n ++n; n");
2130 QVERIFY(ret.isNumber());
2131 QCOMPARE(ret.toInt(), 10);
2132 }
2133 {
2134 QJSValue ret = eng.evaluate(program: "n = 30; i = 0; while (i++ < 10)\n --n; n");
2135 QVERIFY(ret.isNumber());
2136 QCOMPARE(ret.toInt(), 20);
2137 }
2138 {
2139 QJSValue ret = eng.evaluate(program: "o = { a: 0, b: 1, c: 2 }; n = 0; for (i in o)\n ++n; n");
2140 QVERIFY(ret.isNumber());
2141 QCOMPARE(ret.toInt(), 3);
2142 }
2143 {
2144 QJSValue ret = eng.evaluate(program: "o = { a: 0, b: 1, c: 2 }; n = 9; for (i in o)\n --n; n");
2145 QVERIFY(ret.isNumber());
2146 QCOMPARE(ret.toInt(), 6);
2147 }
2148 {
2149 QJSValue ret = eng.evaluate(program: "o = { a: 0, b: 1, c: 2 }; n = 0; for (var i in o)\n ++n; n");
2150 QVERIFY(ret.isNumber());
2151 QCOMPARE(ret.toInt(), 3);
2152 }
2153 {
2154 QJSValue ret = eng.evaluate(program: "o = { a: 0, b: 1, c: 2 }; n = 9; for (var i in o)\n --n; n");
2155 QVERIFY(ret.isNumber());
2156 QCOMPARE(ret.toInt(), 6);
2157 }
2158 {
2159 QJSValue ret = eng.evaluate(program: "o = { n: 3 }; n = 5; with (o)\n ++n; n");
2160 QVERIFY(ret.isNumber());
2161 QCOMPARE(ret.toInt(), 5);
2162 }
2163 {
2164 QJSValue ret = eng.evaluate(program: "o = { n: 3 }; n = 10; with (o)\n --n; n");
2165 QVERIFY(ret.isNumber());
2166 QCOMPARE(ret.toInt(), 10);
2167 }
2168 {
2169 QJSValue ret = eng.evaluate(program: "n = 5; i = 0; do\n ++n; while (++i < 10); n");
2170 QVERIFY(ret.isNumber());
2171 QCOMPARE(ret.toInt(), 15);
2172 }
2173 {
2174 QJSValue ret = eng.evaluate(program: "n = 20; i = 0; do\n --n; while (++i < 10); n");
2175 QVERIFY(ret.isNumber());
2176 QCOMPARE(ret.toInt(), 10);
2177 }
2178
2179 {
2180 QJSValue ret = eng.evaluate(program: "n = 1; i = 0; if (n) i\n++n; n");
2181 QVERIFY(ret.isNumber());
2182 QCOMPARE(ret.toInt(), 2);
2183 }
2184 {
2185 QJSValue ret = eng.evaluate(program: "n = 1; i = 0; if (n) i\n--n; n");
2186 QVERIFY(ret.isNumber());
2187 QCOMPARE(ret.toInt(), 0);
2188 }
2189
2190 {
2191 QJSValue ret = eng.evaluate(program: "if (0)");
2192 QVERIFY(ret.isError());
2193 }
2194 {
2195 QJSValue ret = eng.evaluate(program: "n = 0; if (1) --n;else\n ++n;\n n");
2196 QVERIFY(ret.isNumber());
2197 QCOMPARE(ret.toInt(), -1);
2198 }
2199 {
2200 QJSValue ret = eng.evaluate(program: "while (0)");
2201 QVERIFY(ret.isError());
2202 }
2203 {
2204 QJSValue ret = eng.evaluate(program: "for (;;)");
2205 QVERIFY(ret.isError());
2206 }
2207 {
2208 QJSValue ret = eng.evaluate(program: "for (p in this)");
2209 QVERIFY(ret.isError());
2210 }
2211 {
2212 QJSValue ret = eng.evaluate(program: "with (this)");
2213 QVERIFY(ret.isError());
2214 }
2215 {
2216 QJSValue ret = eng.evaluate(program: "do");
2217 QVERIFY(ret.isError());
2218 }
2219}
2220
2221void tst_QJSEngine::errorConstructors()
2222{
2223 QJSEngine eng;
2224 QStringList prefixes;
2225 prefixes << "" << "Eval" << "Range" << "Reference" << "Syntax" << "Type" << "URI";
2226 for (int x = 0; x < 3; ++x) {
2227 for (int i = 0; i < prefixes.size(); ++i) {
2228 QString name = prefixes.at(i) + QLatin1String("Error");
2229 QString code = QString(i+1, QLatin1Char('\n'));
2230 if (x == 0)
2231 code += QLatin1String("throw ");
2232 else if (x == 1)
2233 code += QLatin1String("new ");
2234 code += name + QLatin1String("()");
2235 QJSValue ret = eng.evaluate(program: code);
2236 QVERIFY(ret.isError());
2237 QVERIFY(ret.toString().startsWith(name));
2238 qDebug() << ret.property(name: "stack").toString();
2239 QCOMPARE(ret.property("lineNumber").toInt(), i+2);
2240 }
2241 }
2242}
2243
2244void tst_QJSEngine::argumentsProperty_globalContext()
2245{
2246 QJSEngine eng;
2247 {
2248 // Unlike function calls, the global context doesn't have an
2249 // arguments property.
2250 QJSValue ret = eng.evaluate(program: "arguments");
2251 QVERIFY(ret.isError());
2252 QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
2253 }
2254 eng.evaluate(program: "arguments = 10");
2255 {
2256 QJSValue ret = eng.evaluate(program: "arguments");
2257 QVERIFY(ret.isNumber());
2258 QCOMPARE(ret.toInt(), 10);
2259 }
2260 QVERIFY(eng.evaluate("delete arguments").toBool());
2261 QVERIFY(eng.globalObject().property("arguments").isUndefined());
2262}
2263
2264void tst_QJSEngine::argumentsProperty_JS()
2265{
2266 {
2267 QJSEngine eng;
2268 eng.evaluate(program: "o = { arguments: 123 }");
2269 QJSValue ret = eng.evaluate(program: "with (o) { arguments; }");
2270 QVERIFY(ret.isNumber());
2271 QCOMPARE(ret.toInt(), 123);
2272 }
2273 {
2274 QJSEngine eng;
2275 QVERIFY(eng.globalObject().property("arguments").isUndefined());
2276 // This is testing ECMA-262 compliance. In function calls, "arguments"
2277 // appears like a local variable, and it can be replaced.
2278 QJSValue ret = eng.evaluate(program: "(function() { arguments = 456; return arguments; })()");
2279 QVERIFY(ret.isNumber());
2280 QCOMPARE(ret.toInt(), 456);
2281 QVERIFY(eng.globalObject().property("arguments").isUndefined());
2282 }
2283}
2284
2285void tst_QJSEngine::jsNumberClass()
2286{
2287 // See ECMA-262 Section 15.7, "Number Objects".
2288
2289 QJSEngine eng;
2290
2291 QJSValue ctor = eng.globalObject().property(name: "Number");
2292 QVERIFY(ctor.property("length").isNumber());
2293 QCOMPARE(ctor.property("length").toNumber(), qreal(1));
2294 QJSValue proto = ctor.property(name: "prototype");
2295 QVERIFY(proto.isObject());
2296 {
2297 QVERIFY(ctor.property("MAX_VALUE").isNumber());
2298 QVERIFY(ctor.property("MIN_VALUE").isNumber());
2299 QVERIFY(ctor.property("NaN").isNumber());
2300 QVERIFY(ctor.property("NEGATIVE_INFINITY").isNumber());
2301 QVERIFY(ctor.property("POSITIVE_INFINITY").isNumber());
2302 QVERIFY(ctor.property("EPSILON").isNumber());
2303 }
2304 QCOMPARE(proto.toNumber(), qreal(0));
2305 QVERIFY(proto.property("constructor").strictlyEquals(ctor));
2306
2307 {
2308 QJSValue ret = eng.evaluate(program: "Number()");
2309 QVERIFY(ret.isNumber());
2310 QCOMPARE(ret.toNumber(), qreal(0));
2311 }
2312 {
2313 QJSValue ret = eng.evaluate(program: "Number(123)");
2314 QVERIFY(ret.isNumber());
2315 QCOMPARE(ret.toNumber(), qreal(123));
2316 }
2317 {
2318 QJSValue ret = eng.evaluate(program: "Number('456')");
2319 QVERIFY(ret.isNumber());
2320 QCOMPARE(ret.toNumber(), qreal(456));
2321 }
2322 {
2323 QJSValue ret = eng.evaluate(program: "new Number()");
2324 QVERIFY(!ret.isNumber());
2325 QVERIFY(ret.isObject());
2326 QCOMPARE(ret.toNumber(), qreal(0));
2327 }
2328 {
2329 QJSValue ret = eng.evaluate(program: "new Number(123)");
2330 QVERIFY(!ret.isNumber());
2331 QVERIFY(ret.isObject());
2332 QCOMPARE(ret.toNumber(), qreal(123));
2333 }
2334 {
2335 QJSValue ret = eng.evaluate(program: "new Number('456')");
2336 QVERIFY(!ret.isNumber());
2337 QVERIFY(ret.isObject());
2338 QCOMPARE(ret.toNumber(), qreal(456));
2339 }
2340
2341 QVERIFY(ctor.property("isFinite").isCallable());
2342 {
2343 QJSValue ret = eng.evaluate(program: "Number.isFinite()");
2344 QVERIFY(ret.isBool());
2345 QCOMPARE(ret.toBool(), false);
2346 }
2347 {
2348 QJSValue ret = eng.evaluate(program: "Number.isFinite(NaN)");
2349 QVERIFY(ret.isBool());
2350 QCOMPARE(ret.toBool(), false);
2351 }
2352 {
2353 QJSValue ret = eng.evaluate(program: "Number.isFinite(Infinity)");
2354 QVERIFY(ret.isBool());
2355 QCOMPARE(ret.toBool(), false);
2356 }
2357 {
2358 QJSValue ret = eng.evaluate(program: "Number.isFinite(-Infinity)");
2359 QVERIFY(ret.isBool());
2360 QCOMPARE(ret.toBool(), false);
2361 }
2362 {
2363 QJSValue ret = eng.evaluate(program: "Number.isFinite(123)");
2364 QVERIFY(ret.isBool());
2365 QCOMPARE(ret.toBool(), true);
2366 }
2367
2368 QVERIFY(ctor.property("isNaN").isCallable());
2369 {
2370 QJSValue ret = eng.evaluate(program: "Number.isNaN()");
2371 QVERIFY(ret.isBool());
2372 QCOMPARE(ret.toBool(), false);
2373 }
2374 {
2375 QJSValue ret = eng.evaluate(program: "Number.isNaN(NaN)");
2376 QVERIFY(ret.isBool());
2377 QCOMPARE(ret.toBool(), true);
2378 }
2379 {
2380 QJSValue ret = eng.evaluate(program: "Number.isNaN(123)");
2381 QVERIFY(ret.isBool());
2382 QCOMPARE(ret.toBool(), false);
2383 }
2384
2385 QVERIFY(proto.property("toString").isCallable());
2386 {
2387 QJSValue ret = eng.evaluate(program: "new Number(123).toString()");
2388 QVERIFY(ret.isString());
2389 QCOMPARE(ret.toString(), QString::fromLatin1("123"));
2390 }
2391 {
2392 QJSValue ret = eng.evaluate(program: "new Number(123).toString(8)");
2393 QVERIFY(ret.isString());
2394 QCOMPARE(ret.toString(), QString::fromLatin1("173"));
2395 }
2396 {
2397 QJSValue ret = eng.evaluate(program: "new Number(123).toString(16)");
2398 QVERIFY(ret.isString());
2399 QCOMPARE(ret.toString(), QString::fromLatin1("7b"));
2400 }
2401 QVERIFY(proto.property("toLocaleString").isCallable());
2402 {
2403 QJSValue ret = eng.evaluate(program: "new Number(123).toLocaleString()");
2404 QVERIFY(ret.isString());
2405 QCOMPARE(ret.toString(), QString::fromLatin1("123"));
2406 }
2407 QVERIFY(proto.property("valueOf").isCallable());
2408 {
2409 QJSValue ret = eng.evaluate(program: "new Number(123).valueOf()");
2410 QVERIFY(ret.isNumber());
2411 QCOMPARE(ret.toNumber(), qreal(123));
2412 }
2413 QVERIFY(proto.property("toExponential").isCallable());
2414 {
2415 QJSValue ret = eng.evaluate(program: "new Number(123).toExponential()");
2416 QVERIFY(ret.isString());
2417 QCOMPARE(ret.toString(), QString::fromLatin1("1.23e+2"));
2418 ret = eng.evaluate(program: "new Number(123).toExponential(1)");
2419 QVERIFY(ret.isString());
2420 QCOMPARE(ret.toString(), QString::fromLatin1("1.2e+2"));
2421 ret = eng.evaluate(program: "new Number(123).toExponential(2)");
2422 QVERIFY(ret.isString());
2423 QCOMPARE(ret.toString(), QString::fromLatin1("1.23e+2"));
2424 ret = eng.evaluate(program: "new Number(123).toExponential(3)");
2425 QVERIFY(ret.isString());
2426 QCOMPARE(ret.toString(), QString::fromLatin1("1.230e+2"));
2427 }
2428 QVERIFY(proto.property("toFixed").isCallable());
2429 {
2430 QJSValue ret = eng.evaluate(program: "new Number(123).toFixed()");
2431 QVERIFY(ret.isString());
2432 QCOMPARE(ret.toString(), QString::fromLatin1("123"));
2433 ret = eng.evaluate(program: "new Number(123).toFixed(1)");
2434 QVERIFY(ret.isString());
2435 QCOMPARE(ret.toString(), QString::fromLatin1("123.0"));
2436 ret = eng.evaluate(program: "new Number(123).toFixed(2)");
2437 QVERIFY(ret.isString());
2438 QCOMPARE(ret.toString(), QString::fromLatin1("123.00"));
2439 }
2440 QVERIFY(proto.property("toPrecision").isCallable());
2441 {
2442 QJSValue ret = eng.evaluate(program: "new Number(123).toPrecision()");
2443 QVERIFY(ret.isString());
2444 QCOMPARE(ret.toString(), QString::fromLatin1("123"));
2445 ret = eng.evaluate(program: "new Number(42).toPrecision(1)");
2446 QVERIFY(ret.isString());
2447 QCOMPARE(ret.toString(), QString::fromLatin1("4e+1"));
2448 ret = eng.evaluate(program: "new Number(42).toPrecision(2)");
2449 QVERIFY(ret.isString());
2450 QCOMPARE(ret.toString(), QString::fromLatin1("42"));
2451 ret = eng.evaluate(program: "new Number(42).toPrecision(3)");
2452 QVERIFY(ret.isString());
2453 QCOMPARE(ret.toString(), QString::fromLatin1("42.0"));
2454 }
2455}
2456
2457void tst_QJSEngine::jsForInStatement_simple()
2458{
2459 QJSEngine eng;
2460 {
2461 QJSValue ret = eng.evaluate(program: "o = { }; r = []; for (var p in o) r[r.length] = p; r");
2462 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2463 QVERIFY(lst.isEmpty());
2464 }
2465 {
2466 QJSValue ret = eng.evaluate(program: "o = { p: 123 }; r = [];"
2467 "for (var p in o) r[r.length] = p; r");
2468 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2469 QCOMPARE(lst.size(), 1);
2470 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2471 }
2472 {
2473 QJSValue ret = eng.evaluate(program: "o = { p: 123, q: 456 }; r = [];"
2474 "for (var p in o) r[r.length] = p; r");
2475 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2476 QCOMPARE(lst.size(), 2);
2477 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2478 QCOMPARE(lst.at(1), QString::fromLatin1("q"));
2479 }
2480}
2481
2482void tst_QJSEngine::jsForInStatement_prototypeProperties()
2483{
2484 QJSEngine eng;
2485 {
2486 QJSValue ret = eng.evaluate(program: "o = { }; o.__proto__ = { p: 123 }; r = [];"
2487 "for (var p in o) r[r.length] = p; r");
2488 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2489 QCOMPARE(lst.size(), 1);
2490 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2491 }
2492 {
2493 QJSValue ret = eng.evaluate(program: "o = { p: 123 }; o.__proto__ = { q: 456 }; r = [];"
2494 "for (var p in o) r[r.length] = p; r");
2495 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2496 QCOMPARE(lst.size(), 2);
2497 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2498 QCOMPARE(lst.at(1), QString::fromLatin1("q"));
2499 }
2500 {
2501 // shadowed property
2502 QJSValue ret = eng.evaluate(program: "o = { p: 123 }; o.__proto__ = { p: 456 }; r = [];"
2503 "for (var p in o) r[r.length] = p; r");
2504 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2505 QCOMPARE(lst.size(), 1);
2506 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2507 }
2508
2509}
2510
2511void tst_QJSEngine::jsForInStatement_mutateWhileIterating()
2512{
2513 QJSEngine eng;
2514 // deleting property during enumeration
2515 {
2516 QJSValue ret = eng.evaluate(program: "o = { p: 123 }; r = [];"
2517 "for (var p in o) { r[r.length] = p; delete r[p]; } r");
2518 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2519 QCOMPARE(lst.size(), 1);
2520 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2521 }
2522 {
2523 QJSValue ret = eng.evaluate(program: "o = { p: 123, q: 456 }; r = [];"
2524 "for (var p in o) { r[r.length] = p; delete o.q; } r");
2525 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2526 QCOMPARE(lst.size(), 1);
2527 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2528 }
2529
2530 // adding property during enumeration
2531 {
2532 QJSValue ret = eng.evaluate(program: "o = { p: 123 }; r = [];"
2533 "for (var p in o) { r[r.length] = p; o.q = 456; } r");
2534 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2535 QCOMPARE(lst.size(), 2);
2536 QCOMPARE(lst.at(0), QString::fromLatin1("p"));
2537 }
2538
2539}
2540
2541void tst_QJSEngine::jsForInStatement_arrays()
2542{
2543 QJSEngine eng;
2544 {
2545 QJSValue ret = eng.evaluate(program: "a = [123, 456]; r = [];"
2546 "for (var p in a) r[r.length] = p; r");
2547 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2548 QCOMPARE(lst.size(), 2);
2549 QCOMPARE(lst.at(0), QString::fromLatin1("0"));
2550 QCOMPARE(lst.at(1), QString::fromLatin1("1"));
2551 }
2552 {
2553 QJSValue ret = eng.evaluate(program: "a = [123, 456]; a.foo = 'bar'; r = [];"
2554 "for (var p in a) r[r.length] = p; r");
2555 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2556 QCOMPARE(lst.size(), 3);
2557 QCOMPARE(lst.at(0), QString::fromLatin1("0"));
2558 QCOMPARE(lst.at(1), QString::fromLatin1("1"));
2559 QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
2560 }
2561 {
2562 QJSValue ret = eng.evaluate(program: "a = [123, 456]; a.foo = 'bar';"
2563 "b = [111, 222, 333]; b.bar = 'baz';"
2564 "a.__proto__ = b; r = [];"
2565 "for (var p in a) r[r.length] = p; r");
2566 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2567 QCOMPARE(lst.size(), 5);
2568 QCOMPARE(lst.at(0), QString::fromLatin1("0"));
2569 QCOMPARE(lst.at(1), QString::fromLatin1("1"));
2570 QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
2571 QCOMPARE(lst.at(3), QString::fromLatin1("2"));
2572 QCOMPARE(lst.at(4), QString::fromLatin1("bar"));
2573 }
2574}
2575
2576void tst_QJSEngine::jsForInStatement_constant()
2577{
2578 QJSEngine eng;
2579 {
2580 QJSValue ret = eng.evaluate(program: "r = true; for (var p in undefined) r = false; r");
2581 QVERIFY(ret.isBool());
2582 QVERIFY(ret.toBool());
2583 }
2584 {
2585 QJSValue ret = eng.evaluate(program: "r = true; for (var p in null) r = false; r");
2586 QVERIFY(ret.isBool());
2587 QVERIFY(ret.toBool());
2588 }
2589 {
2590 QJSValue ret = eng.evaluate(program: "r = false; for (var p in 1) r = true; r");
2591 QVERIFY(ret.isBool());
2592 QVERIFY(!ret.toBool());
2593 }
2594 {
2595 QJSValue ret = eng.evaluate(program: "r = false; for (var p in 'abc') r = true; r");
2596 QVERIFY(ret.isBool());
2597 QVERIFY(ret.toBool());
2598 }
2599}
2600
2601void tst_QJSEngine::with_constant()
2602{
2603 QJSEngine eng;
2604 {
2605 QJSValue ret = eng.evaluate(program: "r = false; with(null) { r= true; } r");
2606 QVERIFY(ret.isError());
2607 }
2608 {
2609 QJSValue ret = eng.evaluate(program: "r = false; with(undefined) { r= true; } r");
2610 QVERIFY(ret.isError());
2611 }
2612 {
2613 QJSValue ret = eng.evaluate(program: "r = false; with(1) { r= true; } r");
2614 QVERIFY(ret.isBool());
2615 QVERIFY(ret.toBool());
2616 }
2617}
2618
2619void tst_QJSEngine::stringObjects()
2620{
2621 // See ECMA-262 Section 15.5, "String Objects".
2622
2623 QJSEngine eng;
2624 QString str("ciao");
2625 // in C++
2626 {
2627 QJSValue obj = eng.evaluate(program: QString::fromLatin1(str: "new String('%0')").arg(a: str));
2628 QCOMPARE(obj.property("length").toInt(), str.length());
2629 for (int i = 0; i < str.length(); ++i) {
2630 QString pname = QString::number(i);
2631 QVERIFY(obj.property(pname).isString());
2632 QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
2633 QVERIFY(!obj.deleteProperty(pname));
2634 obj.setProperty(name: pname, value: 123);
2635 QVERIFY(obj.property(pname).isString());
2636 QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
2637 }
2638 QVERIFY(obj.property("-1").isUndefined());
2639 QVERIFY(obj.property(QString::number(str.length())).isUndefined());
2640
2641 QJSValue val = eng.toScriptValue(value: 123);
2642 obj.setProperty(name: "-1", value: val);
2643 QVERIFY(obj.property("-1").strictlyEquals(val));
2644 obj.setProperty(name: "100", value: val);
2645 QVERIFY(obj.property("100").strictlyEquals(val));
2646 }
2647
2648 {
2649 QJSValue ret = eng.evaluate(program: "s = new String('ciao'); r = []; for (var p in s) r.push(p); r");
2650 QVERIFY(ret.isArray());
2651 QStringList lst = qjsvalue_cast<QStringList>(value: ret);
2652 QCOMPARE(lst.size(), str.length());
2653 for (int i = 0; i < str.length(); ++i)
2654 QCOMPARE(lst.at(i), QString::number(i));
2655
2656 QJSValue ret2 = eng.evaluate(program: "s[0] = 123; s[0]");
2657 QVERIFY(ret2.isString());
2658 QCOMPARE(ret2.toString().length(), 1);
2659 QCOMPARE(ret2.toString().at(0), str.at(0));
2660
2661 QJSValue ret3 = eng.evaluate(program: "s[-1] = 123; s[-1]");
2662 QVERIFY(ret3.isNumber());
2663 QCOMPARE(ret3.toInt(), 123);
2664
2665 QJSValue ret4 = eng.evaluate(program: "s[s.length] = 456; s[s.length]");
2666 QVERIFY(ret4.isNumber());
2667 QCOMPARE(ret4.toInt(), 456);
2668
2669 QJSValue ret5 = eng.evaluate(program: "delete s[0]");
2670 QVERIFY(ret5.isBool());
2671 QVERIFY(!ret5.toBool());
2672
2673 QJSValue ret6 = eng.evaluate(program: "delete s[-1]");
2674 QVERIFY(ret6.isBool());
2675 QVERIFY(ret6.toBool());
2676
2677 QJSValue ret7 = eng.evaluate(program: "delete s[s.length]");
2678 QVERIFY(ret7.isBool());
2679 QVERIFY(ret7.toBool());
2680 }
2681}
2682
2683void tst_QJSEngine::jsStringPrototypeReplaceBugs()
2684{
2685 QJSEngine eng;
2686 // task 212440
2687 {
2688 QJSValue ret = eng.evaluate(program: "replace_args = []; \"a a a\".replace(/(a)/g, function() { replace_args.push(arguments); }); replace_args");
2689 QVERIFY(ret.isArray());
2690 int len = ret.property(name: "length").toInt();
2691 QCOMPARE(len, 3);
2692 for (int i = 0; i < len; ++i) {
2693 QJSValue args = ret.property(arrayIndex: i);
2694 QCOMPARE(args.property("length").toInt(), 4);
2695 QCOMPARE(args.property(0).toString(), QString::fromLatin1("a")); // matched string
2696 QCOMPARE(args.property(1).toString(), QString::fromLatin1("a")); // capture
2697 QVERIFY(args.property(2).isNumber());
2698 QCOMPARE(args.property(2).toInt(), i*2); // index of match
2699 QCOMPARE(args.property(3).toString(), QString::fromLatin1("a a a"));
2700 }
2701 }
2702 // task 212501
2703 {
2704 QJSValue ret = eng.evaluate(program: "\"foo\".replace(/a/g, function() {})");
2705 QVERIFY(ret.isString());
2706 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
2707 }
2708}
2709
2710void tst_QJSEngine::getterSetterThisObject_global()
2711{
2712 {
2713 QJSEngine eng;
2714 // read
2715 eng.evaluate(program: "__defineGetter__('x', function() { return this; });");
2716 {
2717 QJSValue ret = eng.evaluate(program: "x");
2718 QVERIFY(ret.equals(eng.globalObject()));
2719 }
2720 {
2721 QJSValue ret = eng.evaluate(program: "(function() { return x; })()");
2722 QVERIFY(ret.equals(eng.globalObject()));
2723 }
2724 {
2725 QJSValue ret = eng.evaluate(program: "with (this) x");
2726 QVERIFY(ret.equals(eng.globalObject()));
2727 }
2728 {
2729 QJSValue ret = eng.evaluate(program: "with ({}) x");
2730 QVERIFY(ret.equals(eng.globalObject()));
2731 }
2732 {
2733 QJSValue ret = eng.evaluate(program: "(function() { with ({}) return x; })()");
2734 QVERIFY(ret.equals(eng.globalObject()));
2735 }
2736 // write
2737 eng.evaluate(program: "__defineSetter__('x', function() { return this; });");
2738 {
2739 QJSValue ret = eng.evaluate(program: "x = 'foo'");
2740 // SpiderMonkey says setter return value, JSC says RHS.
2741 QVERIFY(ret.isString());
2742 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
2743 }
2744 {
2745 QJSValue ret = eng.evaluate(program: "(function() { return x = 'foo'; })()");
2746 // SpiderMonkey says setter return value, JSC says RHS.
2747 QVERIFY(ret.isString());
2748 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
2749 }
2750 {
2751 QJSValue ret = eng.evaluate(program: "with (this) x = 'foo'");
2752 // SpiderMonkey says setter return value, JSC says RHS.
2753 QVERIFY(ret.isString());
2754 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
2755 }
2756 {
2757 QJSValue ret = eng.evaluate(program: "with ({}) x = 'foo'");
2758 // SpiderMonkey says setter return value, JSC says RHS.
2759 QVERIFY(ret.isString());
2760 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
2761 }
2762 {
2763 QJSValue ret = eng.evaluate(program: "(function() { with ({}) return x = 'foo'; })()");
2764 // SpiderMonkey says setter return value, JSC says RHS.
2765 QVERIFY(ret.isString());
2766 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
2767 }
2768 }
2769}
2770
2771void tst_QJSEngine::getterSetterThisObject_plain()
2772{
2773 {
2774 QJSEngine eng;
2775 eng.evaluate(program: "o = {}");
2776 // read
2777 eng.evaluate(program: "o.__defineGetter__('x', function() { return this; })");
2778 QVERIFY(eng.evaluate("o.x === o").toBool());
2779 QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
2780 QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBool());
2781 eng.evaluate(program: "q = {}; with (o) with (q) x").equals(other: eng.evaluate(program: "o"));
2782 // write
2783 eng.evaluate(program: "o.__defineSetter__('x', function() { return this; });");
2784 // SpiderMonkey says setter return value, JSC says RHS.
2785 QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBool());
2786 QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
2787 QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
2788 }
2789}
2790
2791void tst_QJSEngine::getterSetterThisObject_prototypeChain()
2792{
2793 {
2794 QJSEngine eng;
2795 eng.evaluate(program: "o = {}; p = {}; o.__proto__ = p");
2796 // read
2797 eng.evaluate(program: "p.__defineGetter__('x', function() { return this; })");
2798 QVERIFY(eng.evaluate("o.x === o").toBool());
2799 QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
2800 QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBool());
2801 eng.evaluate(program: "q = {}; with (o) with (q) x").equals(other: eng.evaluate(program: "o"));
2802 eng.evaluate(program: "with (q) with (o) x").equals(other: eng.evaluate(program: "o"));
2803 // write
2804 eng.evaluate(program: "o.__defineSetter__('x', function() { return this; });");
2805 // SpiderMonkey says setter return value, JSC says RHS.
2806 QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBool());
2807 QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
2808 QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
2809 }
2810}
2811
2812void tst_QJSEngine::jsContinueInSwitch()
2813{
2814 // This is testing ECMA-262 compliance, not C++ API.
2815
2816 QJSEngine eng;
2817 // switch - continue
2818 {
2819 QJSValue ret = eng.evaluate(program: "switch (1) { default: continue; }");
2820 QVERIFY(ret.isError());
2821 }
2822 // for - switch - case - continue
2823 {
2824 QJSValue ret = eng.evaluate(program: "j = 0; for (i = 0; i < 100000; ++i) {\n"
2825 " switch (i) {\n"
2826 " case 1: ++j; continue;\n"
2827 " case 100: ++j; continue;\n"
2828 " case 1000: ++j; continue;\n"
2829 " }\n"
2830 "}; j");
2831 QVERIFY(ret.isNumber());
2832 QCOMPARE(ret.toInt(), 3);
2833 }
2834 // for - switch - case - default - continue
2835 {
2836 QJSValue ret = eng.evaluate(program: "j = 0; for (i = 0; i < 100000; ++i) {\n"
2837 " switch (i) {\n"
2838 " case 1: ++j; continue;\n"
2839 " case 100: ++j; continue;\n"
2840 " case 1000: ++j; continue;\n"
2841 " default: if (i < 50000) break; else continue;\n"
2842 " }\n"
2843 "}; j");
2844 QVERIFY(ret.isNumber());
2845 QCOMPARE(ret.toInt(), 3);
2846 }
2847 // switch - for - continue
2848 {
2849 QJSValue ret = eng.evaluate(program: "j = 123; switch (j) {\n"
2850 " case 123:\n"
2851 " for (i = 0; i < 100000; ++i) {\n"
2852 " continue;\n"
2853 " }\n"
2854 "}; i\n");
2855 QVERIFY(ret.isNumber());
2856 QCOMPARE(ret.toInt(), 100000);
2857 }
2858 // switch - switch - continue
2859 {
2860 QJSValue ret = eng.evaluate(program: "i = 1; switch (i) { default: switch (i) { case 1: continue; } }");
2861 QVERIFY(ret.isError());
2862 }
2863 // for - switch - switch - continue
2864 {
2865 QJSValue ret = eng.evaluate(program: "j = 0; for (i = 0; i < 100000; ++i) {\n"
2866 " switch (i) {\n"
2867 " case 1:\n"
2868 " switch (i) {\n"
2869 " case 1: ++j; continue;\n"
2870 " }\n"
2871 " }\n"
2872 "}; j");
2873 QVERIFY(ret.isNumber());
2874 QCOMPARE(ret.toInt(), 1);
2875 }
2876 // switch - for - switch - continue
2877 {
2878 QJSValue ret = eng.evaluate(program: "j = 123; switch (j) {\n"
2879 " case 123:\n"
2880 " for (i = 0; i < 100000; ++i) {\n"
2881 " switch (i) {\n"
2882 " case 1:\n"
2883 " ++j; continue;\n"
2884 " }\n"
2885 " }\n"
2886 "}; i\n");
2887 QVERIFY(ret.isNumber());
2888 QCOMPARE(ret.toInt(), 100000);
2889 }
2890}
2891
2892void tst_QJSEngine::jsShadowReadOnlyPrototypeProperty()
2893{
2894 QJSEngine eng;
2895 QVERIFY(eng.evaluate("o = {}; o.__proto__ = parseInt; o.length").isNumber());
2896 QVERIFY(eng.evaluate("o.length = 123; o.length").toInt() != 123);
2897 QVERIFY(!eng.evaluate("o.hasOwnProperty('length')").toBool());
2898}
2899
2900void tst_QJSEngine::jsReservedWords_data()
2901{
2902 QTest::addColumn<QString>(name: "word");
2903 QTest::newRow(dataTag: "break") << QString("break");
2904 QTest::newRow(dataTag: "case") << QString("case");
2905 QTest::newRow(dataTag: "catch") << QString("catch");
2906 QTest::newRow(dataTag: "continue") << QString("continue");
2907 QTest::newRow(dataTag: "default") << QString("default");
2908 QTest::newRow(dataTag: "delete") << QString("delete");
2909 QTest::newRow(dataTag: "do") << QString("do");
2910 QTest::newRow(dataTag: "else") << QString("else");
2911 QTest::newRow(dataTag: "false") << QString("false");
2912 QTest::newRow(dataTag: "finally") << QString("finally");
2913 QTest::newRow(dataTag: "for") << QString("for");
2914 QTest::newRow(dataTag: "function") << QString("function");
2915 QTest::newRow(dataTag: "if") << QString("if");
2916 QTest::newRow(dataTag: "in") << QString("in");
2917 QTest::newRow(dataTag: "instanceof") << QString("instanceof");
2918 QTest::newRow(dataTag: "new") << QString("new");
2919 QTest::newRow(dataTag: "null") << QString("null");
2920 QTest::newRow(dataTag: "return") << QString("return");
2921 QTest::newRow(dataTag: "switch") << QString("switch");
2922 QTest::newRow(dataTag: "this") << QString("this");
2923 QTest::newRow(dataTag: "throw") << QString("throw");
2924 QTest::newRow(dataTag: "true") << QString("true");
2925 QTest::newRow(dataTag: "try") << QString("try");
2926 QTest::newRow(dataTag: "typeof") << QString("typeof");
2927 QTest::newRow(dataTag: "var") << QString("var");
2928 QTest::newRow(dataTag: "void") << QString("void");
2929 QTest::newRow(dataTag: "while") << QString("while");
2930 QTest::newRow(dataTag: "with") << QString("with");
2931}
2932
2933void tst_QJSEngine::jsReservedWords()
2934{
2935 // See ECMA-262 Section 7.6.1, "Reserved Words".
2936 // We prefer that the implementation is less strict than the spec; e.g.
2937 // it's good to allow reserved words as identifiers in object literals,
2938 // and when accessing properties using dot notation.
2939
2940 QFETCH(QString, word);
2941 {
2942 QJSEngine eng;
2943 QJSValue ret = eng.evaluate(program: word + " = 123");
2944 QVERIFY(ret.isError());
2945 QString str = ret.toString();
2946 QVERIFY(str.startsWith("SyntaxError") || str.startsWith("ReferenceError"));
2947 }
2948 {
2949 QJSEngine eng;
2950 QJSValue ret = eng.evaluate(program: "var " + word + " = 123");
2951 QVERIFY(ret.isError());
2952 QVERIFY(ret.toString().startsWith("SyntaxError"));
2953 }
2954 {
2955 QJSEngine eng;
2956 QJSValue ret = eng.evaluate(program: "o = {}; o." + word + " = 123");
2957 // in the old back-end, in SpiderMonkey and in v8, this is allowed, but not in JSC
2958 QVERIFY(!ret.isError());
2959 QVERIFY(ret.strictlyEquals(eng.evaluate("o." + word)));
2960 }
2961 {
2962 QJSEngine eng;
2963 QJSValue ret = eng.evaluate(program: "o = { " + word + ": 123 }");
2964 // in the old back-end, in SpiderMonkey and in v8, this is allowed, but not in JSC
2965 QVERIFY(!ret.isError());
2966 QVERIFY(ret.property(word).isNumber());
2967 }
2968 {
2969 // SpiderMonkey allows this, but we don't
2970 QJSEngine eng;
2971 QJSValue ret = eng.evaluate(program: "function " + word + "() {}");
2972 QVERIFY(ret.isError());
2973 QVERIFY(ret.toString().startsWith("SyntaxError"));
2974 }
2975}
2976
2977void tst_QJSEngine::jsFutureReservedWords_data()
2978{
2979 QTest::addColumn<QString>(name: "word");
2980 QTest::addColumn<bool>(name: "allowed");
2981 QTest::newRow(dataTag: "abstract") << QString("abstract") << true;
2982 QTest::newRow(dataTag: "boolean") << QString("boolean") << true;
2983 QTest::newRow(dataTag: "byte") << QString("byte") << true;
2984 QTest::newRow(dataTag: "char") << QString("char") << true;
2985 QTest::newRow(dataTag: "class") << QString("class") << false;
2986 QTest::newRow(dataTag: "const") << QString("const") << false;
2987 QTest::newRow(dataTag: "debugger") << QString("debugger") << false;
2988 QTest::newRow(dataTag: "double") << QString("double") << true;
2989 QTest::newRow(dataTag: "enum") << QString("enum") << false;
2990 QTest::newRow(dataTag: "export") << QString("export") << false;
2991 QTest::newRow(dataTag: "extends") << QString("extends") << false;
2992 QTest::newRow(dataTag: "final") << QString("final") << true;
2993 QTest::newRow(dataTag: "float") << QString("float") << true;
2994 QTest::newRow(dataTag: "goto") << QString("goto") << true;
2995 QTest::newRow(dataTag: "implements") << QString("implements") << true;
2996 QTest::newRow(dataTag: "import") << QString("import") << false;
2997 QTest::newRow(dataTag: "int") << QString("int") << true;
2998 QTest::newRow(dataTag: "interface") << QString("interface") << true;
2999 QTest::newRow(dataTag: "long") << QString("long") << true;
3000 QTest::newRow(dataTag: "native") << QString("native") << true;
3001 QTest::newRow(dataTag: "package") << QString("package") << true;
3002 QTest::newRow(dataTag: "private") << QString("private") << true;
3003 QTest::newRow(dataTag: "protected") << QString("protected") << true;
3004 QTest::newRow(dataTag: "public") << QString("public") << true;
3005 QTest::newRow(dataTag: "short") << QString("short") << true;
3006 QTest::newRow(dataTag: "static") << QString("static") << true;
3007 QTest::newRow(dataTag: "super") << QString("super") << false;
3008 QTest::newRow(dataTag: "synchronized") << QString("synchronized") << true;
3009 QTest::newRow(dataTag: "throws") << QString("throws") << true;
3010 QTest::newRow(dataTag: "transient") << QString("transient") << true;
3011 QTest::newRow(dataTag: "volatile") << QString("volatile") << true;
3012}
3013
3014void tst_QJSEngine::jsFutureReservedWords()
3015{
3016 // See ECMA-262 Section 7.6.1.2, "Future Reserved Words".
3017 // In real-world implementations, most of these words are
3018 // actually allowed as normal identifiers.
3019
3020 QFETCH(QString, word);
3021 QFETCH(bool, allowed);
3022 {
3023 QJSEngine eng;
3024 QJSValue ret = eng.evaluate(program: word + " = 123");
3025 QCOMPARE(!ret.isError(), allowed);
3026 }
3027 {
3028 QJSEngine eng;
3029 QJSValue ret = eng.evaluate(program: "var " + word + " = 123");
3030 QCOMPARE(!ret.isError(), allowed);
3031 }
3032 {
3033 QJSEngine eng;
3034 QJSValue ret = eng.evaluate(program: "o = {}; o." + word + " = 123");
3035
3036 QCOMPARE(ret.isNumber(), true);
3037 QCOMPARE(!ret.isError(), true);
3038 }
3039 {
3040 QJSEngine eng;
3041 QJSValue ret = eng.evaluate(program: "o = { " + word + ": 123 }");
3042 QCOMPARE(!ret.isError(), true);
3043 }
3044}
3045
3046void tst_QJSEngine::jsThrowInsideWithStatement()
3047{
3048 // This is testing ECMA-262 compliance, not C++ API.
3049
3050 // task 209988
3051 QJSEngine eng;
3052 {
3053 QJSValue ret = eng.evaluate(
3054 program: "try {"
3055 " o = { bad : \"bug\" };"
3056 " with (o) {"
3057 " throw 123;"
3058 " }"
3059 "} catch (e) {"
3060 " bad;"
3061 "}");
3062 QVERIFY(ret.isError());
3063 QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
3064 }
3065 {
3066 QJSValue ret = eng.evaluate(
3067 program: "try {"
3068 " o = { bad : \"bug\" };"
3069 " with (o) {"
3070 " throw 123;"
3071 " }"
3072 "} finally {"
3073 " bad;"
3074 "}");
3075 QVERIFY(ret.isError());
3076 QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
3077 }
3078 {
3079 QJSValue ret = eng.evaluate(
3080 program: "o = { bug : \"no bug\" };"
3081 "with (o) {"
3082 " try {"
3083 " throw 123;"
3084 " } finally {"
3085 " bug;"
3086 " }"
3087 "}");
3088 QVERIFY(!ret.isError());
3089 QVERIFY(ret.isNumber());
3090 QCOMPARE(ret.toInt(), 123);
3091 }
3092 {
3093 QJSValue ret = eng.evaluate(
3094 program: "o = { bug : \"no bug\" };"
3095 "with (o) {"
3096 " throw 123;"
3097 "}");
3098 QVERIFY(ret.isNumber());
3099 QJSValue ret2 = eng.evaluate(program: "bug");
3100 QVERIFY(ret2.isError());
3101 QVERIFY(ret2.toString().contains(QString::fromLatin1("ReferenceError")));
3102 }
3103}
3104
3105void tst_QJSEngine::reentrancy_globalObjectProperties()
3106{
3107 QJSEngine eng1;
3108 QJSEngine eng2;
3109 QVERIFY(eng2.globalObject().property("a").isUndefined());
3110 eng1.evaluate(program: "a = 10");
3111 QVERIFY(eng1.globalObject().property("a").isNumber());
3112 QVERIFY(eng2.globalObject().property("a").isUndefined());
3113 eng2.evaluate(program: "a = 20");
3114 QVERIFY(eng2.globalObject().property("a").isNumber());
3115 QCOMPARE(eng1.globalObject().property("a").toInt(), 10);
3116}
3117
3118void tst_QJSEngine::reentrancy_Array()
3119{
3120 // weird bug with JSC backend
3121 {
3122 QJSEngine eng;
3123 QCOMPARE(eng.evaluate("Array()").toString(), QString());
3124 eng.evaluate(program: "Array.prototype.toString");
3125 QCOMPARE(eng.evaluate("Array()").toString(), QString());
3126 }
3127 {
3128 QJSEngine eng;
3129 QCOMPARE(eng.evaluate("Array()").toString(), QString());
3130 }
3131}
3132
3133void tst_QJSEngine::reentrancy_objectCreation()
3134{
3135 // Owned by JS engine, as newQObject() sets JS ownership explicitly
3136 QObject *temp = new QObject();
3137
3138 QJSEngine eng1;
3139 QJSEngine eng2;
3140 {
3141 QDateTime dt = QDateTime::currentDateTime();
3142 QJSValue d1 = eng1.toScriptValue(value: dt);
3143 QJSValue d2 = eng2.toScriptValue(value: dt);
3144 QCOMPARE(d1.toDateTime(), d2.toDateTime());
3145 QCOMPARE(d2.toDateTime(), d1.toDateTime());
3146 }
3147 {
3148 QJSValue r1 = eng1.evaluate(program: "new RegExp('foo', 'gim')");
3149 QJSValue r2 = eng2.evaluate(program: "new RegExp('foo', 'gim')");
3150 QCOMPARE(qjsvalue_cast<QRegExp>(r1), qjsvalue_cast<QRegExp>(r2));
3151 QCOMPARE(qjsvalue_cast<QRegExp>(r2), qjsvalue_cast<QRegExp>(r1));
3152 QCOMPARE(qjsvalue_cast<QRegularExpression>(r1), qjsvalue_cast<QRegularExpression>(r2));
3153 QCOMPARE(qjsvalue_cast<QRegularExpression>(r2), qjsvalue_cast<QRegularExpression>(r1));
3154 }
3155 {
3156 QJSValue o1 = eng1.newQObject(object: temp);
3157 QJSValue o2 = eng2.newQObject(object: temp);
3158 QCOMPARE(o1.toQObject(), o2.toQObject());
3159 QCOMPARE(o2.toQObject(), o1.toQObject());
3160 }
3161}
3162
3163void tst_QJSEngine::jsIncDecNonObjectProperty()
3164{
3165 // This is testing ECMA-262 compliance, not C++ API.
3166
3167 QJSEngine eng;
3168 {
3169 QJSValue ret = eng.evaluate(program: "var a; a.n++");
3170 QVERIFY(ret.isError());
3171 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3172 }
3173 {
3174 QJSValue ret = eng.evaluate(program: "var a; a.n--");
3175 QVERIFY(ret.isError());
3176 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3177 }
3178 {
3179 QJSValue ret = eng.evaluate(program: "var a = null; a.n++");
3180 QVERIFY(ret.isError());
3181 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3182 }
3183 {
3184 QJSValue ret = eng.evaluate(program: "var a = null; a.n--");
3185 QVERIFY(ret.isError());
3186 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3187 }
3188 {
3189 QJSValue ret = eng.evaluate(program: "var a; ++a.n");
3190 QVERIFY(ret.isError());
3191 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3192 }
3193 {
3194 QJSValue ret = eng.evaluate(program: "var a; --a.n");
3195 QVERIFY(ret.isError());
3196 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3197 }
3198 {
3199 QJSValue ret = eng.evaluate(program: "var a; a.n += 1");
3200 QVERIFY(ret.isError());
3201 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3202 }
3203 {
3204 QJSValue ret = eng.evaluate(program: "var a; a.n -= 1");
3205 QVERIFY(ret.isError());
3206 QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
3207 }
3208 {
3209 QJSValue ret = eng.evaluate(program: "var a = 'ciao'; a.length++");
3210 QVERIFY(ret.isNumber());
3211 QCOMPARE(ret.toInt(), 4);
3212 }
3213 {
3214 QJSValue ret = eng.evaluate(program: "var a = 'ciao'; a.length--");
3215 QVERIFY(ret.isNumber());
3216 QCOMPARE(ret.toInt(), 4);
3217 }
3218 {
3219 QJSValue ret = eng.evaluate(program: "var a = 'ciao'; ++a.length");
3220 QVERIFY(ret.isNumber());
3221 QCOMPARE(ret.toInt(), 5);
3222 }
3223 {
3224 QJSValue ret = eng.evaluate(program: "var a = 'ciao'; --a.length");
3225 QVERIFY(ret.isNumber());
3226 QCOMPARE(ret.toInt(), 3);
3227 }
3228}
3229
3230void tst_QJSEngine::JSONparse()
3231{
3232 QJSEngine eng;
3233 QJSValue ret = eng.evaluate(program: "var json=\"{\\\"1\\\": null}\"; JSON.parse(json);");
3234 QVERIFY(ret.isObject());
3235}
3236
3237void tst_QJSEngine::arraySort()
3238{
3239 // tests that calling Array.sort with a bad sort function doesn't cause issues
3240 // Using std::sort is e.g. not safe when used with a bad sort function and causes
3241 // crashes
3242 QJSEngine eng;
3243 eng.evaluate(program: "function crashMe() {"
3244 " var data = [];"
3245 " for (var i = 0; i < 50; i++) {"
3246 " data[i] = 'whatever';"
3247 " }"
3248 " data.sort(function(a, b) {"
3249 " return -1;"
3250 " });"
3251 "}"
3252 "crashMe();");
3253}
3254
3255void tst_QJSEngine::lookupOnDisappearingProperty()
3256{
3257 QJSEngine eng;
3258 QJSValue func = eng.evaluate(program: "(function(){\"use strict\"; return eval(\"(function(obj) { return obj.someProperty; })\")})()");
3259 QVERIFY(func.isCallable());
3260
3261 QJSValue o = eng.newObject();
3262 o.setProperty(QStringLiteral("someProperty"), value: 42);
3263
3264 QCOMPARE(func.call(QJSValueList()<< o).toInt(), 42);
3265
3266 o = eng.newObject();
3267 QVERIFY(func.call(QJSValueList()<< o).isUndefined());
3268 QVERIFY(func.call(QJSValueList()<< o).isUndefined());
3269}
3270
3271void tst_QJSEngine::arrayConcat()
3272{
3273 QJSEngine eng;
3274 QJSValue v = eng.evaluate(program: "var x = [1, 2, 3, 4, 5, 6];"
3275 "var y = [];"
3276 "for (var i = 0; i < 5; ++i)"
3277 " x.shift();"
3278 "for (var i = 10; i < 13; ++i)"
3279 " x.push(i);"
3280 "x.toString();");
3281 QCOMPARE(v.toString(), QString::fromLatin1("6,10,11,12"));
3282}
3283
3284void tst_QJSEngine::recursiveBoundFunctions()
3285{
3286
3287 QJSEngine eng;
3288 QJSValue v = eng.evaluate(program: "function foo(x, y, z)"
3289 "{ return this + x + y + z; }"
3290 "var bar = foo.bind(-1, 10);"
3291 "var baz = bar.bind(-2, 20);"
3292 "baz(30)");
3293 QCOMPARE(v.toInt(), 59);
3294}
3295
3296static QRegExp minimal(QRegExp r) { r.setMinimal(true); return r; }
3297
3298void tst_QJSEngine::qRegExpInport_data()
3299{
3300 QTest::addColumn<QRegExp>(name: "rx");
3301 QTest::addColumn<QString>(name: "string");
3302 QTest::addColumn<QString>(name: "matched");
3303
3304 QTest::newRow(dataTag: "normal") << QRegExp("(test|foo)") << "test _ foo _ test _ Foo";
3305 QTest::newRow(dataTag: "normal2") << QRegExp("(Test|Foo)") << "test _ foo _ test _ Foo";
3306 QTest::newRow(dataTag: "case insensitive)") << QRegExp("(test|foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
3307 QTest::newRow(dataTag: "case insensitive2)") << QRegExp("(Test|Foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
3308 QTest::newRow(dataTag: "b(a*)(b*)") << QRegExp("b(a*)(b*)", Qt::CaseInsensitive) << "aaabbBbaAabaAaababaaabbaaab";
3309 QTest::newRow(dataTag: "greedy") << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp2) << "aaaabaaba";
3310 QTest::newRow(dataTag: "willcard") << QRegExp("*.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "file.txt";
3311 QTest::newRow(dataTag: "willcard 2") << QRegExp("a?b.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "ab.txt abb.rtc acb.txt";
3312 QTest::newRow(dataTag: "slash") << QRegExp("g/.*/s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string/string/string";
3313 QTest::newRow(dataTag: "slash2") << QRegExp("g / .* / s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string / string / string";
3314 QTest::newRow(dataTag: "fixed") << QRegExp("a*aa.a(ba)*a\\ba", Qt::CaseInsensitive, QRegExp::FixedString) << "aa*aa.a(ba)*a\\ba";
3315 QTest::newRow(dataTag: "fixed insensitive") << QRegExp("A*A", Qt::CaseInsensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
3316 QTest::newRow(dataTag: "fixed sensitive") << QRegExp("A*A", Qt::CaseSensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
3317 QTest::newRow(dataTag: "html") << QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2) << "<b>bold</b><i>italic</i><b>bold</b>";
3318 QTest::newRow(dataTag: "html minimal") << minimal(r: QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2)) << "<b>bold</b><i>italic</i><b>bold</b>";
3319 QTest::newRow(dataTag: "aaa") << QRegExp("a{2,5}") << "aAaAaaaaaAa";
3320 QTest::newRow(dataTag: "aaa minimal") << minimal(r: QRegExp("a{2,5}")) << "aAaAaaaaaAa";
3321 QTest::newRow(dataTag: "minimal") << minimal(r: QRegExp(".*\\} [*8]")) << "}?} ?} *";
3322 QTest::newRow(dataTag: ".? minimal") << minimal(r: QRegExp(".?")) << ".?";
3323 QTest::newRow(dataTag: ".+ minimal") << minimal(r: QRegExp(".+")) << ".+";
3324 QTest::newRow(dataTag: "[.?] minimal") << minimal(r: QRegExp("[.?]")) << ".?";
3325 QTest::newRow(dataTag: "[.+] minimal") << minimal(r: QRegExp("[.+]")) << ".+";
3326}
3327
3328void tst_QJSEngine::qRegExpInport()
3329{
3330 QFETCH(QRegExp, rx);
3331 QFETCH(QString, string);
3332
3333 QJSEngine eng;
3334 QJSValue rexp;
3335 rexp = eng.toScriptValue(value: rx);
3336
3337 QCOMPARE(rexp.isRegExp(), true);
3338 QCOMPARE(rexp.isCallable(), false);
3339
3340 QJSValue func = eng.evaluate(program: "(function(string, regexp) { return string.match(regexp); })");
3341 QJSValue result = func.call(args: QJSValueList() << string << rexp);
3342
3343 rx.indexIn(str: string);
3344 for (int i = 0; i <= rx.captureCount(); i++) {
3345 QCOMPARE(result.property(i).toString(), rx.cap(i));
3346 }
3347}
3348
3349void tst_QJSEngine::qRegularExpressionImport_data()
3350{
3351 QTest::addColumn<QRegularExpression>(name: "rx");
3352 QTest::addColumn<QString>(name: "string");
3353 QTest::addColumn<QString>(name: "matched");
3354
3355 QTest::newRow(dataTag: "normal") << QRegularExpression("(test|foo)") << "test _ foo _ test _ Foo";
3356 QTest::newRow(dataTag: "normal2") << QRegularExpression("(Test|Foo)") << "test _ foo _ test _ Foo";
3357 QTest::newRow(dataTag: "case insensitive") << QRegularExpression("(test|foo)", QRegularExpression::CaseInsensitiveOption) << "test _ foo _ test _ Foo";
3358 QTest::newRow(dataTag: "case insensitive2") << QRegularExpression("(Test|Foo)", QRegularExpression::CaseInsensitiveOption) << "test _ foo _ test _ Foo";
3359 QTest::newRow(dataTag: "b(a*)(b*)") << QRegularExpression("b(a*)(b*)", QRegularExpression::CaseInsensitiveOption) << "aaabbBbaAabaAaababaaabbaaab";
3360 QTest::newRow(dataTag: "greedy") << QRegularExpression("a*(a*)", QRegularExpression::CaseInsensitiveOption) << "aaaabaaba";
3361 QTest::newRow(dataTag: "wildcard") << QRegularExpression(".*\\.txt") << "file.txt";
3362 QTest::newRow(dataTag: "wildcard 2") << QRegularExpression("a.b\\.txt") << "ab.txt abb.rtc acb.txt";
3363 QTest::newRow(dataTag: "slash") << QRegularExpression("g/.*/s", QRegularExpression::CaseInsensitiveOption) << "string/string/string";
3364 QTest::newRow(dataTag: "slash2") << QRegularExpression("g / .* / s", QRegularExpression::CaseInsensitiveOption) << "string / string / string";
3365 QTest::newRow(dataTag: "fixed") << QRegularExpression("a\\*aa\\.a\\(ba\\)\\*a\\\\ba", QRegularExpression::CaseInsensitiveOption) << "aa*aa.a(ba)*a\\ba";
3366 QTest::newRow(dataTag: "fixed insensitive") << QRegularExpression("A\\*A", QRegularExpression::CaseInsensitiveOption) << "a*A A*a A*A a*a";
3367 QTest::newRow(dataTag: "fixed sensitive") << QRegularExpression("A\\*A") << "a*A A*a A*A a*a";
3368 QTest::newRow(dataTag: "html") << QRegularExpression("<b>(.*)</b>") << "<b>bold</b><i>italic</i><b>bold</b>";
3369 QTest::newRow(dataTag: "html minimal") << QRegularExpression("^<b>(.*)</b>$") << "<b>bold</b><i>italic</i><b>bold</b>";
3370 QTest::newRow(dataTag: "aaa") << QRegularExpression("a{2,5}") << "aAaAaaaaaAa";
3371 QTest::newRow(dataTag: "aaa minimal") << QRegularExpression("^a{2,5}$") << "aAaAaaaaaAa";
3372 QTest::newRow(dataTag: "minimal") << QRegularExpression("^.*\\} [*8]$") << "}?} ?} *";
3373 QTest::newRow(dataTag: ".? minimal") << QRegularExpression("^.?$") << ".?";
3374 QTest::newRow(dataTag: ".+ minimal") << QRegularExpression("^.+$") << ".+";
3375 QTest::newRow(dataTag: "[.?] minimal") << QRegularExpression("^[.?]$") << ".?";
3376 QTest::newRow(dataTag: "[.+] minimal") << QRegularExpression("^[.+]$") << ".+";
3377}
3378
3379void tst_QJSEngine::qRegularExpressionImport()
3380{
3381 QFETCH(QRegularExpression, rx);
3382 QFETCH(QString, string);
3383
3384 QJSEngine eng;
3385 QJSValue rexp;
3386 rexp = eng.toScriptValue(value: rx);
3387
3388 QCOMPARE(rexp.isRegExp(), true);
3389 QCOMPARE(rexp.isCallable(), false);
3390
3391 QJSValue func = eng.evaluate(program: "(function(string, regexp) { return string.match(regexp); })");
3392 QJSValue result = func.call(args: QJSValueList() << string << rexp);
3393
3394 const QRegularExpressionMatch match = rx.match(subject: string);
3395 for (int i = 0; i <= match.lastCapturedIndex(); i++)
3396 QCOMPARE(result.property(i).toString(), match.captured(i));
3397}
3398
3399// QScriptValue::toDateTime() returns a local time, whereas JS dates
3400// are always stored as UTC. Qt Script must respect the current time
3401// zone, and correctly adjust for daylight saving time that may be in
3402// effect at a given date (QTBUG-9770).
3403void tst_QJSEngine::dateRoundtripJSQtJS()
3404{
3405#ifdef Q_OS_WIN
3406 QSKIP("This test fails on Windows due to a bug in QDateTime.");
3407#endif
3408 qint64 secs = QDate(2009, 1, 1).startOfDay(spec: Qt::UTC).toSecsSinceEpoch();
3409 QJSEngine eng;
3410 for (int i = 0; i < 8000; ++i) {
3411 QJSValue jsDate = eng.evaluate(program: QString::fromLatin1(str: "new Date(%0)").arg(a: secs * 1000.0));
3412 QDateTime qtDate = jsDate.toDateTime();
3413 QJSValue jsDate2 = eng.toScriptValue(value: qtDate);
3414 if (jsDate2.toNumber() != jsDate.toNumber())
3415 QFAIL(qPrintable(jsDate.toString()));
3416 secs += 2*60*60;
3417 }
3418}
3419
3420void tst_QJSEngine::dateRoundtripQtJSQt()
3421{
3422#ifdef Q_OS_WIN
3423 QSKIP("This test fails on Windows due to a bug in QDateTime.");
3424#endif
3425 QDateTime qtDate = QDate(2009, 1, 1).startOfDay();
3426 QJSEngine eng;
3427 for (int i = 0; i < 8000; ++i) {
3428 QJSValue jsDate = eng.toScriptValue(value: qtDate);
3429 QDateTime qtDate2 = jsDate.toDateTime();
3430 if (qtDate2 != qtDate)
3431 QFAIL(qPrintable(qtDate.toString()));
3432 qtDate = qtDate.addSecs(secs: 2*60*60);
3433 }
3434}
3435
3436void tst_QJSEngine::dateConversionJSQt()
3437{
3438#ifdef Q_OS_WIN
3439 QSKIP("This test fails on Windows due to a bug in QDateTime.");
3440#endif
3441 qint64 secs = QDate(2009, 1, 1).startOfDay(spec: Qt::UTC).toSecsSinceEpoch();
3442 QJSEngine eng;
3443 for (int i = 0; i < 8000; ++i) {
3444 QJSValue jsDate = eng.evaluate(program: QString::fromLatin1(str: "new Date(%0)").arg(a: secs * 1000.0));
3445 QDateTime qtDate = jsDate.toDateTime();
3446 QString qtUTCDateStr = qtDate.toUTC().toString(format: Qt::ISODate);
3447 QString jsUTCDateStr = jsDate.property(name: "toISOString").callWithInstance(instance: jsDate).toString();
3448 jsUTCDateStr.remove(i: jsUTCDateStr.length() - 5, len: 4); // get rid of milliseconds (".000")
3449 if (qtUTCDateStr != jsUTCDateStr)
3450 QFAIL(qPrintable(jsDate.toString()));
3451 secs += 2*60*60;
3452 }
3453}
3454
3455void tst_QJSEngine::dateConversionQtJS()
3456{
3457 QDateTime qtDate = QDate(2009, 1, 1).startOfDay();
3458 QJSEngine eng;
3459 for (int i = 0; i < 8000; ++i) {
3460 QJSValue jsDate = eng.toScriptValue(value: qtDate);
3461 QString jsUTCDateStr = jsDate.property(name: "toISOString").callWithInstance(instance: jsDate).toString();
3462 QString qtUTCDateStr = qtDate.toUTC().toString(format: Qt::ISODate);
3463 jsUTCDateStr.remove(i: jsUTCDateStr.length() - 5, len: 4); // get rid of milliseconds (".000")
3464 if (jsUTCDateStr != qtUTCDateStr)
3465 QFAIL(qPrintable(qtDate.toString()));
3466 qtDate = qtDate.addSecs(secs: 2*60*60);
3467 }
3468}
3469
3470void tst_QJSEngine::functionPrototypeExtensions()
3471{
3472 // QJS adds connect and disconnect properties to Function.prototype.
3473 QJSEngine eng;
3474 QJSValue funProto = eng.globalObject().property(name: "Function").property(name: "prototype");
3475 QVERIFY(funProto.isCallable());
3476 QVERIFY(funProto.property("connect").isCallable());
3477 QVERIFY(funProto.property("disconnect").isCallable());
3478
3479 // No properties should appear in for-in statements.
3480 QJSValue props = eng.evaluate(program: "props = []; for (var p in Function.prototype) props.push(p); props");
3481 QVERIFY(props.isArray());
3482 QCOMPARE(props.property("length").toInt(), 0);
3483}
3484
3485class ThreadedTestEngine : public QThread {
3486 Q_OBJECT;
3487
3488public:
3489 int result = 0;
3490
3491 ThreadedTestEngine() {}
3492
3493 void run() {
3494 QJSEngine firstEngine;
3495 QJSEngine secondEngine;
3496 QJSValue value = firstEngine.evaluate(program: "1");
3497 result = secondEngine.evaluate(program: "1 + " + QString::number(value.toInt())).toInt();
3498 }
3499};
3500
3501void tst_QJSEngine::threadedEngine()
3502{
3503 ThreadedTestEngine thread1;
3504 ThreadedTestEngine thread2;
3505 thread1.start();
3506 thread2.start();
3507 thread1.wait();
3508 thread2.wait();
3509 QCOMPARE(thread1.result, 2);
3510 QCOMPARE(thread2.result, 2);
3511}
3512
3513void tst_QJSEngine::functionDeclarationsInConditionals()
3514{
3515 // Visibility of function declarations inside blocks is limited to the block
3516 QJSEngine eng;
3517 QJSValue result = eng.evaluate(program: "if (true) {\n"
3518 " function blah() { return false; }\n"
3519 "} else {\n"
3520 " function blah() { return true; }\n"
3521 "}\n"
3522 "blah();");
3523 QVERIFY(result.isError());
3524}
3525
3526void tst_QJSEngine::arrayPop_QTBUG_35979()
3527{
3528 QJSEngine eng;
3529 QJSValue result = eng.evaluate(program: ""
3530 "var x = [1, 2]\n"
3531 "x.pop()\n"
3532 "x[1] = 3\n"
3533 "x.toString()\n");
3534 QCOMPARE(result.toString(), QString("1,3"));
3535}
3536
3537void tst_QJSEngine::array_unshift_QTBUG_52065()
3538{
3539 QJSEngine eng;
3540 QJSValue result = eng.evaluate(program: "[1, 2, 3, 4, 5, 6, 7, 8, 9]");
3541 QJSValue unshift = result.property(QStringLiteral("unshift"));
3542 unshift.callWithInstance(instance: result, args: QJSValueList() << QJSValue(0));
3543
3544 int len = result.property(QStringLiteral("length")).toInt();
3545 QCOMPARE(len, 10);
3546
3547 for (int i = 0; i < len; ++i)
3548 QCOMPARE(result.property(i).toInt(), i);
3549}
3550
3551void tst_QJSEngine::array_join_QTBUG_53672()
3552{
3553 QJSEngine eng;
3554 QJSValue result = eng.evaluate(program: "Array.prototype.join.call(0)");
3555 QVERIFY(result.isString());
3556 QCOMPARE(result.toString(), QString(""));
3557}
3558
3559void tst_QJSEngine::regexpLastMatch()
3560{
3561 QJSEngine eng;
3562
3563 QCOMPARE(eng.evaluate("RegExp.input").toString(), QString());
3564
3565 QJSValue hasProperty;
3566
3567 for (int i = 1; i < 9; ++i) {
3568 hasProperty = eng.evaluate(program: "RegExp.hasOwnProperty(\"$" + QString::number(i) + "\")");
3569 QVERIFY(hasProperty.isBool());
3570 QVERIFY(hasProperty.toBool());
3571 }
3572
3573 hasProperty = eng.evaluate(program: "RegExp.hasOwnProperty(\"$0\")");
3574 QVERIFY(hasProperty.isBool());
3575 QVERIFY(!hasProperty.toBool());
3576
3577 hasProperty = eng.evaluate(program: "RegExp.hasOwnProperty(\"$10\")");
3578 QVERIFY(!hasProperty.toBool());
3579
3580 hasProperty = eng.evaluate(program: "RegExp.hasOwnProperty(\"lastMatch\")");
3581 QVERIFY(hasProperty.toBool());
3582 hasProperty = eng.evaluate(program: "RegExp.hasOwnProperty(\"$&\")");
3583 QVERIFY(hasProperty.toBool());
3584
3585 QJSValue result = eng.evaluate(program: ""
3586 "var re = /h(el)l(o)/\n"
3587 "var text = \"blah hello world\"\n"
3588 "text.match(re)\n");
3589 QVERIFY(!result.isError());
3590 QJSValue match = eng.evaluate(program: "RegExp.$1");
3591 QCOMPARE(match.toString(), QString("el"));
3592 match = eng.evaluate(program: "RegExp.$2");
3593 QCOMPARE(match.toString(), QString("o"));
3594 for (int i = 3; i <= 9; ++i) {
3595 match = eng.evaluate(program: "RegExp.$" + QString::number(i));
3596 QVERIFY(match.isString());
3597 QCOMPARE(match.toString(), QString());
3598 }
3599 QCOMPARE(eng.evaluate("RegExp.input").toString(), QString("blah hello world"));
3600 QCOMPARE(eng.evaluate("RegExp.lastParen").toString(), QString("o"));
3601 QCOMPARE(eng.evaluate("RegExp.leftContext").toString(), QString("blah "));
3602 QCOMPARE(eng.evaluate("RegExp.rightContext").toString(), QString(" world"));
3603
3604 QCOMPARE(eng.evaluate("RegExp.lastMatch").toString(), QString("hello"));
3605
3606 result = eng.evaluate(program: ""
3607 "var re = /h(ello)/\n"
3608 "var text = \"hello\"\n"
3609 "text.match(re)\n");
3610 QVERIFY(!result.isError());
3611 match = eng.evaluate(program: "RegExp.$1");
3612 QCOMPARE(match.toString(), QString("ello"));
3613 for (int i = 2; i <= 9; ++i) {
3614 match = eng.evaluate(program: "RegExp.$" + QString::number(i));
3615 QVERIFY(match.isString());
3616 QCOMPARE(match.toString(), QString());
3617 }
3618
3619}
3620
3621void tst_QJSEngine::regexpLastIndex()
3622{
3623 QJSEngine eng;
3624 QJSValue result;
3625 result = eng.evaluate(program: "function test(text, rx) {"
3626 " var res;"
3627 " while (res = rx.exec(text)) { "
3628 " return true;"
3629 " }"
3630 " return false;"
3631 " }"
3632 "function tester(text) {"
3633 " return test(text, /,\\s*/g);"
3634 "}");
3635 QVERIFY(!result.isError());
3636
3637 result = eng.evaluate(program: "tester(\", \\n\");");
3638 QVERIFY(result.toBool());
3639 result = eng.evaluate(program: "tester(\", \\n\");");
3640 QVERIFY(result.toBool());
3641}
3642
3643void tst_QJSEngine::indexedAccesses()
3644{
3645 QJSEngine engine;
3646 QJSValue v = engine.evaluate(program: "function foo() { return 1[1] } foo()");
3647 QVERIFY(v.isUndefined());
3648 v = engine.evaluate(program: "function foo() { return /x/[1] } foo()");
3649 QVERIFY(v.isUndefined());
3650 v = engine.evaluate(program: "function foo() { return \"xy\"[1] } foo()");
3651 QVERIFY(v.isString());
3652 v = engine.evaluate(program: "function foo() { return \"xy\"[2] } foo()");
3653 QVERIFY(v.isUndefined());
3654}
3655
3656void tst_QJSEngine::prototypeChainGc()
3657{
3658 QJSEngine engine;
3659
3660 QJSValue getProto = engine.evaluate(program: "Object.getPrototypeOf");
3661
3662 QJSValue factory = engine.evaluate(program: "(function() { return Object.create(Object.create({})); })");
3663 QVERIFY(factory.isCallable());
3664 QJSValue obj = factory.call();
3665 engine.collectGarbage();
3666
3667 QJSValue proto = getProto.call(args: QJSValueList() << obj);
3668 proto = getProto.call(args: QJSValueList() << proto);
3669 QVERIFY(proto.isObject());
3670}
3671
3672void tst_QJSEngine::prototypeChainGc_QTBUG38299()
3673{
3674 QJSEngine engine;
3675 engine.evaluate(program: "var mapping = {"
3676 "'prop1': \"val1\",\n"
3677 "'prop2': \"val2\"\n"
3678 "}\n"
3679 "\n"
3680 "delete mapping.prop2\n"
3681 "delete mapping.prop1\n"
3682 "\n");
3683 // Don't hang!
3684 engine.collectGarbage();
3685}
3686
3687void tst_QJSEngine::dynamicProperties()
3688{
3689 {
3690 QJSEngine engine;
3691 QObject *obj = new QObject;
3692 QJSValue wrapper = engine.newQObject(object: obj);
3693 wrapper.setProperty(name: "someRandomProperty", value: 42);
3694 QCOMPARE(wrapper.property("someRandomProperty").toInt(), 42);
3695 QVERIFY(!qmlContext(obj));
3696 }
3697 {
3698 QQmlEngine qmlEngine;
3699 QQmlComponent component(&qmlEngine);
3700 component.setData("import QtQml 2.0; QtObject { property QtObject subObject: QtObject {} }", baseUrl: QUrl());
3701 QObject *root = component.create(context: nullptr);
3702 QVERIFY(root);
3703 QVERIFY(qmlContext(root));
3704
3705 QJSValue wrapper = qmlEngine.newQObject(object: root);
3706 wrapper.setProperty(name: "someRandomProperty", value: 42);
3707 QVERIFY(!wrapper.hasProperty("someRandomProperty"));
3708
3709 QObject *subObject = qvariant_cast<QObject*>(v: root->property(name: "subObject"));
3710 QVERIFY(subObject);
3711 QVERIFY(qmlContext(subObject));
3712
3713 wrapper = qmlEngine.newQObject(object: subObject);
3714 wrapper.setProperty(name: "someRandomProperty", value: 42);
3715 QVERIFY(!wrapper.hasProperty("someRandomProperty"));
3716 }
3717}
3718
3719class EvaluateWrapper : public QObject
3720{
3721 Q_OBJECT
3722public:
3723 EvaluateWrapper(QJSEngine *engine)
3724 : engine(engine)
3725 {}
3726
3727public slots:
3728 QJSValue cppEvaluate(const QString &program)
3729 {
3730 return engine->evaluate(program);
3731 }
3732
3733private:
3734 QJSEngine *engine;
3735};
3736
3737void tst_QJSEngine::scopeOfEvaluate()
3738{
3739 QJSEngine engine;
3740 QJSValue wrapper = engine.newQObject(object: new EvaluateWrapper(&engine));
3741
3742 engine.evaluate(program: "testVariable = 42");
3743
3744 QJSValue function = engine.evaluate(program: "(function(evalWrapper){\n"
3745 "var testVariable = 100; \n"
3746 "try { \n"
3747 " return evalWrapper.cppEvaluate(\"(function() { return testVariable; })\")\n"
3748 " ()\n"
3749 "} catch (e) {}\n"
3750 "})");
3751 QVERIFY(function.isCallable());
3752 QJSValue result = function.call(args: QJSValueList() << wrapper);
3753 QCOMPARE(result.toInt(), 42);
3754}
3755
3756void tst_QJSEngine::callConstants()
3757{
3758 QJSEngine engine;
3759 engine.evaluate(program: "function f() {\n"
3760 " var one; one();\n"
3761 " var two = null; two();\n"
3762 "}\n");
3763 QJSValue exceptionResult = engine.evaluate(program: "true()");
3764 QCOMPARE(exceptionResult.toString(), QString("TypeError: true is not a function"));
3765}
3766
3767void tst_QJSEngine::installTranslationFunctions()
3768{
3769 QJSEngine eng;
3770 QJSValue global = eng.globalObject();
3771 QVERIFY(global.property("qsTranslate").isUndefined());
3772 QVERIFY(global.property("QT_TRANSLATE_NOOP").isUndefined());
3773 QVERIFY(global.property("qsTr").isUndefined());
3774 QVERIFY(global.property("QT_TR_NOOP").isUndefined());
3775 QVERIFY(global.property("qsTrId").isUndefined());
3776 QVERIFY(global.property("QT_TRID_NOOP").isUndefined());
3777
3778 eng.installExtensions(extensions: QJSEngine::TranslationExtension);
3779 QVERIFY(global.property("qsTranslate").isCallable());
3780 QVERIFY(global.property("QT_TRANSLATE_NOOP").isCallable());
3781 QVERIFY(global.property("qsTr").isCallable());
3782 QVERIFY(global.property("QT_TR_NOOP").isCallable());
3783 QVERIFY(global.property("qsTrId").isCallable());
3784 QVERIFY(global.property("QT_TRID_NOOP").isCallable());
3785
3786 {
3787 QJSValue ret = eng.evaluate(program: "qsTr('foo')");
3788 QVERIFY(ret.isString());
3789 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
3790 }
3791 {
3792 QJSValue ret = eng.evaluate(program: "qsTranslate('foo', 'bar')");
3793 QVERIFY(ret.isString());
3794 QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
3795 }
3796 {
3797 QJSValue ret = eng.evaluate(program: "QT_TR_NOOP('foo')");
3798 QVERIFY(ret.isString());
3799 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
3800 }
3801 {
3802 QJSValue ret = eng.evaluate(program: "QT_TRANSLATE_NOOP('foo', 'bar')");
3803 QVERIFY(ret.isString());
3804 QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
3805 }
3806
3807 {
3808 QJSValue ret = eng.evaluate(program: "qsTrId('foo')");
3809 QVERIFY(ret.isString());
3810 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
3811 }
3812 {
3813 QJSValue ret = eng.evaluate(program: "QT_TRID_NOOP('foo')");
3814 QVERIFY(ret.isString());
3815 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
3816 }
3817 {
3818 QJSValue ret = eng.evaluate(program: "qsTr('%1').arg('foo')");
3819 QVERIFY(ret.isString());
3820 QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
3821 }
3822 QVERIFY(eng.evaluate("QT_TRID_NOOP()").isUndefined());
3823}
3824
3825class TranslationScope
3826{
3827public:
3828 TranslationScope(const QString &fileName)
3829 {
3830 translator.load(filename: fileName);
3831 QCoreApplication::instance()->installTranslator(messageFile: &translator);
3832 }
3833 ~TranslationScope()
3834 {
3835 QCoreApplication::instance()->removeTranslator(messageFile: &translator);
3836 }
3837
3838private:
3839 QTranslator translator;
3840};
3841
3842void tst_QJSEngine::translateScript_data()
3843{
3844 QTest::addColumn<QString>(name: "expression");
3845 QTest::addColumn<QString>(name: "fileName");
3846 QTest::addColumn<QString>(name: "expectedTranslation");
3847
3848 QString fileName = QString::fromLatin1(str: "translatable.js");
3849 // Top-level
3850 QTest::newRow(dataTag: "qsTr('One')@translatable.js")
3851 << QString::fromLatin1(str: "qsTr('One')") << fileName << QString::fromLatin1(str: "En");
3852 QTest::newRow(dataTag: "qsTr('Hello')@translatable.js")
3853 << QString::fromLatin1(str: "qsTr('Hello')") << fileName << QString::fromLatin1(str: "Hallo");
3854 // From function
3855 QTest::newRow(dataTag: "(function() { return qsTr('One'); })()@translatable.js")
3856 << QString::fromLatin1(str: "(function() { return qsTr('One'); })()") << fileName << QString::fromLatin1(str: "En");
3857 QTest::newRow(dataTag: "(function() { return qsTr('Hello'); })()@translatable.js")
3858 << QString::fromLatin1(str: "(function() { return qsTr('Hello'); })()") << fileName << QString::fromLatin1(str: "Hallo");
3859 // Plural
3860 QTest::newRow(dataTag: "qsTr('%n message(s) saved', '', 1)@translatable.js")
3861 << QString::fromLatin1(str: "qsTr('%n message(s) saved', '', 1)") << fileName << QString::fromLatin1(str: "1 melding lagret");
3862 QTest::newRow(dataTag: "qsTr('%n message(s) saved', '', 3).arg@translatable.js")
3863 << QString::fromLatin1(str: "qsTr('%n message(s) saved', '', 3)") << fileName << QString::fromLatin1(str: "3 meldinger lagret");
3864
3865 // Top-level
3866 QTest::newRow(dataTag: "qsTranslate('FooContext', 'Two')@translatable.js")
3867 << QString::fromLatin1(str: "qsTranslate('FooContext', 'Two')") << fileName << QString::fromLatin1(str: "To");
3868 QTest::newRow(dataTag: "qsTranslate('FooContext', 'Goodbye')@translatable.js")
3869 << QString::fromLatin1(str: "qsTranslate('FooContext', 'Goodbye')") << fileName << QString::fromLatin1(str: "Farvel");
3870 // From eval
3871 QTest::newRow(dataTag: "eval('qsTranslate(\\'FooContext\\', \\'Two\\')')@translatable.js")
3872 << QString::fromLatin1(str: "eval('qsTranslate(\\'FooContext\\', \\'Two\\')')") << fileName << QString::fromLatin1(str: "To");
3873 QTest::newRow(dataTag: "eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')@translatable.js")
3874 << QString::fromLatin1(str: "eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')") << fileName << QString::fromLatin1(str: "Farvel");
3875
3876 QTest::newRow(dataTag: "qsTranslate('FooContext', 'Goodbye', '')@translatable.js")
3877 << QString::fromLatin1(str: "qsTranslate('FooContext', 'Goodbye', '')") << fileName << QString::fromLatin1(str: "Farvel");
3878
3879 QTest::newRow(dataTag: "qsTranslate('FooContext', 'Goodbye', '', 42)@translatable.js")
3880 << QString::fromLatin1(str: "qsTranslate('FooContext', 'Goodbye', '', 42)") << fileName << QString::fromLatin1(str: "Goodbye");
3881
3882 QTest::newRow(dataTag: "qsTr('One', 'not the same one')@translatable.js")
3883 << QString::fromLatin1(str: "qsTr('One', 'not the same one')") << fileName << QString::fromLatin1(str: "Enda en");
3884
3885 QTest::newRow(dataTag: "qsTr('One', 'not the same one', 42)@translatable.js")
3886 << QString::fromLatin1(str: "qsTr('One', 'not the same one', 42)") << fileName << QString::fromLatin1(str: "One");
3887
3888 // Plural
3889 QTest::newRow(dataTag: "qsTranslate('FooContext', '%n fooish bar(s) found', '', 1)@translatable.js")
3890 << QString::fromLatin1(str: "qsTranslate('FooContext', '%n fooish bar(s) found', '', 1)") << fileName << QString::fromLatin1(str: "1 fooaktig bar funnet");
3891 QTest::newRow(dataTag: "qsTranslate('FooContext', '%n fooish bar(s) found', '', 2)@translatable.js")
3892 << QString::fromLatin1(str: "qsTranslate('FooContext', '%n fooish bar(s) found', '', 2)") << fileName << QString::fromLatin1(str: "2 fooaktige barer funnet");
3893
3894 // Don't exist in translation
3895 QTest::newRow(dataTag: "qsTr('Three')@translatable.js")
3896 << QString::fromLatin1(str: "qsTr('Three')") << fileName << QString::fromLatin1(str: "Three");
3897 QTest::newRow(dataTag: "qsTranslate('FooContext', 'So long')@translatable.js")
3898 << QString::fromLatin1(str: "qsTranslate('FooContext', 'So long')") << fileName << QString::fromLatin1(str: "So long");
3899 QTest::newRow(dataTag: "qsTranslate('BarContext', 'Goodbye')@translatable.js")
3900 << QString::fromLatin1(str: "qsTranslate('BarContext', 'Goodbye')") << fileName << QString::fromLatin1(str: "Goodbye");
3901
3902 // Translate strings from the second script (translatable2.js)
3903
3904 QString fileName2 = QString::fromLatin1(str: "translatable2.js");
3905 QTest::newRow(dataTag: "qsTr('Three')@translatable2.js")
3906 << QString::fromLatin1(str: "qsTr('Three')") << fileName2 << QString::fromLatin1(str: "Tre");
3907 QTest::newRow(dataTag: "qsTr('Happy birthday!')@translatable2.js")
3908 << QString::fromLatin1(str: "qsTr('Happy birthday!')") << fileName2 << QString::fromLatin1(str: "Gratulerer med dagen!");
3909
3910 // Not translated because translation is only in translatable.js
3911 QTest::newRow(dataTag: "qsTr('One')@translatable2.js")
3912 << QString::fromLatin1(str: "qsTr('One')") << fileName2 << QString::fromLatin1(str: "One");
3913 QTest::newRow(dataTag: "(function() { return qsTr('One'); })()@translatable2.js")
3914 << QString::fromLatin1(str: "(function() { return qsTr('One'); })()") << fileName2 << QString::fromLatin1(str: "One");
3915
3916 // For qsTranslate() the filename shouldn't matter
3917 QTest::newRow(dataTag: "qsTranslate('FooContext', 'Two')@translatable2.js")
3918 << QString::fromLatin1(str: "qsTranslate('FooContext', 'Two')") << fileName2 << QString::fromLatin1(str: "To");
3919 QTest::newRow(dataTag: "qsTranslate('BarContext', 'Congratulations!')@translatable.js")
3920 << QString::fromLatin1(str: "qsTranslate('BarContext', 'Congratulations!')") << fileName << QString::fromLatin1(str: "Gratulerer!");
3921}
3922
3923void tst_QJSEngine::translateScript()
3924{
3925 QFETCH(QString, expression);
3926 QFETCH(QString, fileName);
3927 QFETCH(QString, expectedTranslation);
3928
3929 QJSEngine engine;
3930
3931 TranslationScope tranScope(":/translations/translatable_la");
3932 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
3933
3934 QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
3935}
3936
3937void tst_QJSEngine::translateScript_crossScript()
3938{
3939 QJSEngine engine;
3940 TranslationScope tranScope(":/translations/translatable_la");
3941 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
3942
3943 QString fileName = QString::fromLatin1(str: "translatable.js");
3944 QString fileName2 = QString::fromLatin1(str: "translatable2.js");
3945 // qsTr() should use the innermost filename as context
3946 engine.evaluate(program: "function foo(s) { return bar(s); }", fileName);
3947 engine.evaluate(program: "function bar(s) { return qsTr(s); }", fileName: fileName2);
3948 QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Tre"));
3949 QCOMPARE(engine.evaluate("bar('Three')", fileName).toString(), QString::fromLatin1("Tre"));
3950 QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("One"));
3951
3952 engine.evaluate(program: "function foo(s) { return bar(s); }", fileName: fileName2);
3953 engine.evaluate(program: "function bar(s) { return qsTr(s); }", fileName);
3954 QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Three"));
3955 QCOMPARE(engine.evaluate("bar('One')", fileName).toString(), QString::fromLatin1("En"));
3956 QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("En"));
3957}
3958
3959void tst_QJSEngine::translateScript_trNoOp()
3960{
3961 QJSEngine engine;
3962 TranslationScope tranScope(":/translations/translatable_la");
3963 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
3964
3965 QVERIFY(engine.evaluate("QT_TR_NOOP()").isUndefined());
3966 QCOMPARE(engine.evaluate("QT_TR_NOOP('One')").toString(), QString::fromLatin1("One"));
3967
3968 QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP()").isUndefined());
3969 QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP('FooContext')").isUndefined());
3970 QCOMPARE(engine.evaluate("QT_TRANSLATE_NOOP('FooContext', 'Two')").toString(), QString::fromLatin1("Two"));
3971}
3972
3973void tst_QJSEngine::translateScript_callQsTrFromCpp()
3974{
3975 QJSEngine engine;
3976 TranslationScope tranScope(":/translations/translatable_la");
3977 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
3978
3979 // There is no context, but it shouldn't crash
3980 QCOMPARE(engine.globalObject().property("qsTr").call(QJSValueList() << "One").toString(), QString::fromLatin1("One"));
3981}
3982
3983void tst_QJSEngine::translateWithInvalidArgs_data()
3984{
3985 QTest::addColumn<QString>(name: "expression");
3986 QTest::addColumn<QString>(name: "expectedError");
3987
3988 QTest::newRow(dataTag: "qsTr()") << "qsTr()" << "Error: qsTr() requires at least one argument";
3989 QTest::newRow(dataTag: "qsTr(123)") << "qsTr(123)" << "Error: qsTr(): first argument (sourceText) must be a string";
3990 QTest::newRow(dataTag: "qsTr('foo', 123)") << "qsTr('foo', 123)" << "Error: qsTr(): second argument (disambiguation) must be a string";
3991 QTest::newRow(dataTag: "qsTr('foo', 'bar', 'baz')") << "qsTr('foo', 'bar', 'baz')" << "Error: qsTr(): third argument (n) must be a number";
3992 QTest::newRow(dataTag: "qsTr('foo', 'bar', true)") << "qsTr('foo', 'bar', true)" << "Error: qsTr(): third argument (n) must be a number";
3993
3994 QTest::newRow(dataTag: "qsTranslate()") << "qsTranslate()" << "Error: qsTranslate() requires at least two arguments";
3995 QTest::newRow(dataTag: "qsTranslate('foo')") << "qsTranslate('foo')" << "Error: qsTranslate() requires at least two arguments";
3996 QTest::newRow(dataTag: "qsTranslate(123, 'foo')") << "qsTranslate(123, 'foo')" << "Error: qsTranslate(): first argument (context) must be a string";
3997 QTest::newRow(dataTag: "qsTranslate('foo', 123)") << "qsTranslate('foo', 123)" << "Error: qsTranslate(): second argument (sourceText) must be a string";
3998 QTest::newRow(dataTag: "qsTranslate('foo', 'bar', 123)") << "qsTranslate('foo', 'bar', 123)" << "Error: qsTranslate(): third argument (disambiguation) must be a string";
3999
4000 QTest::newRow(dataTag: "qsTrId()") << "qsTrId()" << "Error: qsTrId() requires at least one argument";
4001 QTest::newRow(dataTag: "qsTrId(123)") << "qsTrId(123)" << "TypeError: qsTrId(): first argument (id) must be a string";
4002 QTest::newRow(dataTag: "qsTrId('foo', 'bar')") << "qsTrId('foo', 'bar')" << "TypeError: qsTrId(): second argument (n) must be a number";
4003}
4004
4005void tst_QJSEngine::translateWithInvalidArgs()
4006{
4007 QFETCH(QString, expression);
4008 QFETCH(QString, expectedError);
4009 QJSEngine engine;
4010 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
4011 QJSValue result = engine.evaluate(program: expression);
4012 QVERIFY(result.isError());
4013 QCOMPARE(result.toString(), expectedError);
4014}
4015
4016void tst_QJSEngine::translationContext_data()
4017{
4018 QTest::addColumn<QString>(name: "path");
4019 QTest::addColumn<QString>(name: "text");
4020 QTest::addColumn<QString>(name: "expectedTranslation");
4021
4022 QTest::newRow(dataTag: "translatable.js") << "translatable.js" << "One" << "En";
4023 QTest::newRow(dataTag: "/translatable.js") << "/translatable.js" << "One" << "En";
4024 QTest::newRow(dataTag: "/foo/translatable.js") << "/foo/translatable.js" << "One" << "En";
4025 QTest::newRow(dataTag: "/foo/bar/translatable.js") << "/foo/bar/translatable.js" << "One" << "En";
4026 QTest::newRow(dataTag: "./translatable.js") << "./translatable.js" << "One" << "En";
4027 QTest::newRow(dataTag: "../translatable.js") << "../translatable.js" << "One" << "En";
4028 QTest::newRow(dataTag: "foo/translatable.js") << "foo/translatable.js" << "One" << "En";
4029 QTest::newRow(dataTag: "file:///home/qt/translatable.js") << "file:///home/qt/translatable.js" << "One" << "En";
4030 QTest::newRow(dataTag: ":/resources/translatable.js") << ":/resources/translatable.js" << "One" << "En";
4031 QTest::newRow(dataTag: "/translatable.js.foo") << "/translatable.js.foo" << "One" << "En";
4032 QTest::newRow(dataTag: "/translatable.txt") << "/translatable.txt" << "One" << "En";
4033 QTest::newRow(dataTag: "translatable") << "translatable" << "One" << "En";
4034 QTest::newRow(dataTag: "foo/translatable") << "foo/translatable" << "One" << "En";
4035
4036 QTest::newRow(dataTag: "translatable.js/") << "translatable.js/" << "One" << "One";
4037 QTest::newRow(dataTag: "nosuchscript.js") << "" << "One" << "One";
4038 QTest::newRow(dataTag: "(empty)") << "" << "One" << "One";
4039}
4040
4041void tst_QJSEngine::translationContext()
4042{
4043 TranslationScope tranScope(":/translations/translatable_la");
4044
4045 QJSEngine engine;
4046 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
4047
4048 QFETCH(QString, path);
4049 QFETCH(QString, text);
4050 QFETCH(QString, expectedTranslation);
4051 QJSValue ret = engine.evaluate(program: QString::fromLatin1(str: "qsTr('%0')").arg(a: text), fileName: path);
4052 QVERIFY(ret.isString());
4053 QCOMPARE(ret.toString(), expectedTranslation);
4054}
4055
4056void tst_QJSEngine::translateScriptIdBased()
4057{
4058 QJSEngine engine;
4059
4060 TranslationScope tranScope(":/translations/idtranslatable_la");
4061 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
4062
4063 QString fileName = QString::fromLatin1(str: "idtranslatable.js");
4064
4065 QHash<QString, QString> expectedTranslations;
4066 expectedTranslations["qtn_foo_bar"] = "First string";
4067 expectedTranslations["qtn_needle"] = "Second string";
4068 expectedTranslations["qtn_haystack"] = "Third string";
4069 expectedTranslations["qtn_bar_baz"] = "Fourth string";
4070
4071 QHash<QString, QString>::const_iterator it;
4072 for (it = expectedTranslations.constBegin(); it != expectedTranslations.constEnd(); ++it) {
4073 for (int x = 0; x < 2; ++x) {
4074 QString fn;
4075 if (x)
4076 fn = fileName;
4077 // Top-level
4078 QCOMPARE(engine.evaluate(QString::fromLatin1("qsTrId('%0')")
4079 .arg(it.key()), fn).toString(),
4080 it.value());
4081 QCOMPARE(engine.evaluate(QString::fromLatin1("QT_TRID_NOOP('%0')")
4082 .arg(it.key()), fn).toString(),
4083 it.key());
4084 // From function
4085 QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return qsTrId('%0'); })()")
4086 .arg(it.key()), fn).toString(),
4087 it.value());
4088 QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return QT_TRID_NOOP('%0'); })()")
4089 .arg(it.key()), fn).toString(),
4090 it.key());
4091 }
4092 }
4093
4094 // Plural form
4095 QCOMPARE(engine.evaluate("qsTrId('qtn_bar_baz', 10)").toString(),
4096 QString::fromLatin1("10 fooish bar(s) found"));
4097 QCOMPARE(engine.evaluate("qsTrId('qtn_foo_bar', 10)").toString(),
4098 QString::fromLatin1("qtn_foo_bar")); // Doesn't have plural
4099}
4100
4101// How to add a new test row:
4102// - Find a nice list of Unicode characters to choose from
4103// - Write source string/context/comment in .js using Unicode escape sequences (\uABCD)
4104// - Update corresponding .ts file (e.g. lupdate foo.js -ts foo.ts -codecfortr UTF-8)
4105// - Enter translation in Linguist
4106// - Update corresponding .qm file (e.g. lrelease foo.ts)
4107// - Evaluate script that performs translation; make sure the correct result is returned
4108// (e.g. by setting the resulting string as the text of a QLabel and visually verifying
4109// that it looks the same as what you entered in Linguist :-) )
4110// - Generate the expectedTranslation column data using toUtf8().toHex()
4111void tst_QJSEngine::translateScriptUnicode_data()
4112{
4113 QTest::addColumn<QString>(name: "expression");
4114 QTest::addColumn<QString>(name: "fileName");
4115 QTest::addColumn<QString>(name: "expectedTranslation");
4116
4117 QString fileName = QString::fromLatin1(str: "translatable-unicode.js");
4118 QTest::newRow(dataTag: "qsTr('H\\u2082O')@translatable-unicode.js")
4119 << QString::fromLatin1(str: "qsTr('H\\u2082O')") << fileName << QString::fromUtf8(str: "\xcd\xbb\xcd\xbc\xcd\xbd");
4120 QTest::newRow(dataTag: "qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')@translatable-unicode.js")
4121 << QString::fromLatin1(str: "qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << fileName << QString::fromUtf8(str: "\xd7\x91\xd7\x9a\xd7\xa2");
4122 QTest::newRow(dataTag: "qsTr('\\u0391\\u0392\\u0393')@translatable-unicode.js")
4123 << QString::fromLatin1(str: "qsTr('\\u0391\\u0392\\u0393')") << fileName << QString::fromUtf8(str: "\xd3\x9c\xd2\xb4\xd1\xbc");
4124 QTest::newRow(dataTag: "qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')@translatable-unicode.js")
4125 << QString::fromLatin1(str: "qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')") << fileName << QString::fromUtf8(str: "\xd8\xae\xd8\xb3\xd8\xb3");
4126 QTest::newRow(dataTag: "qsTr('H\\u2082O', 'not the same H\\u2082O')@translatable-unicode.js")
4127 << QString::fromLatin1(str: "qsTr('H\\u2082O', 'not the same H\\u2082O')") << fileName << QString::fromUtf8(str: "\xd4\xb6\xd5\x8a\xd5\x92");
4128 QTest::newRow(dataTag: "qsTr('H\\u2082O')")
4129 << QString::fromLatin1(str: "qsTr('H\\u2082O')") << QString() << QString::fromUtf8(str: "\x48\xe2\x82\x82\x4f");
4130 QTest::newRow(dataTag: "qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')")
4131 << QString::fromLatin1(str: "qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << QString() << QString::fromUtf8(str: "\xd7\x91\xd7\x9a\xd7\xa2");
4132}
4133
4134void tst_QJSEngine::translateScriptUnicode()
4135{
4136 QFETCH(QString, expression);
4137 QFETCH(QString, fileName);
4138 QFETCH(QString, expectedTranslation);
4139
4140 QJSEngine engine;
4141
4142 TranslationScope tranScope(":/translations/translatable-unicode");
4143 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
4144
4145 QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
4146}
4147
4148void tst_QJSEngine::translateScriptUnicodeIdBased_data()
4149{
4150 QTest::addColumn<QString>(name: "expression");
4151 QTest::addColumn<QString>(name: "expectedTranslation");
4152
4153 QTest::newRow(dataTag: "qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1'')")
4154 << QString::fromLatin1(str: "qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1')") << QString::fromUtf8(str: "\xc6\xa7\xc6\xb0\xc6\x88\xc8\xbc\xc8\x9d\xc8\xbf\xc8\x99");
4155 QTest::newRow(dataTag: "qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')")
4156 << QString::fromLatin1(str: "qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')") << QString::fromUtf8(str: "\xc7\xa0\xc8\xa1\xc8\x8b\xc8\x85\xc8\x95");
4157 QTest::newRow(dataTag: "qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)")
4158 << QString::fromLatin1(str: "qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)") << QString::fromUtf8(str: "\x31\x30\x20\xc6\x92\xc6\xa1\xc7\x92\x28\xc8\x99\x29");
4159 QTest::newRow(dataTag: "qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')")
4160 << QString::fromLatin1(str: "qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')") << QString::fromUtf8(str: "\xc6\x91\xc6\xb0\xc7\xb9");
4161 QTest::newRow(dataTag: "qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')")
4162 << QString::fromLatin1(str: "qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')") << QString::fromUtf8(str: "\xc7\x8d\xc6\x80\xc6\xa8\xc6\x90\xc6\x9e\xc6\xab");
4163}
4164
4165void tst_QJSEngine::translateScriptUnicodeIdBased()
4166{
4167 QFETCH(QString, expression);
4168 QFETCH(QString, expectedTranslation);
4169
4170 QJSEngine engine;
4171
4172 TranslationScope tranScope(":/translations/idtranslatable-unicode");
4173 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
4174
4175 QCOMPARE(engine.evaluate(expression).toString(), expectedTranslation);
4176}
4177
4178void tst_QJSEngine::translateFromBuiltinCallback()
4179{
4180 QJSEngine eng;
4181 eng.installExtensions(extensions: QJSEngine::TranslationExtension);
4182
4183 // Callback has no translation context.
4184 eng.evaluate(program: "function foo() { qsTr('foo'); }");
4185
4186 // Stack at translation time will be:
4187 // qsTr, foo, forEach, global
4188 // qsTr() needs to walk to the outer-most (global) frame before it finds
4189 // a translation context, and this should not crash.
4190 eng.evaluate(program: "[10,20].forEach(foo)", fileName: "script.js");
4191}
4192
4193void tst_QJSEngine::translationFilePath_data()
4194{
4195 QTest::addColumn<QString>(name: "filename");
4196
4197 QTest::newRow(dataTag: "relative") << QStringLiteral("script.js");
4198 QTest::newRow(dataTag: "absolute unix") << QStringLiteral("/script.js");
4199 QTest::newRow(dataTag: "absolute /windows/") << QStringLiteral("c:/script.js");
4200#ifdef Q_OS_WIN
4201 QTest::newRow("absolute \\windows\\") << QStringLiteral("c:\\script.js");
4202#endif
4203 QTest::newRow(dataTag: "relative url") << QStringLiteral("file://script.js");
4204 QTest::newRow(dataTag: "absolute url unix") << QStringLiteral("file:///script.js");
4205 QTest::newRow(dataTag: "absolute url windows") << QStringLiteral("file://c:/script.js");
4206}
4207
4208class DummyTranslator : public QTranslator
4209{
4210 Q_OBJECT
4211
4212public:
4213 DummyTranslator(const char *sourceText)
4214 : srcTxt(sourceText)
4215 {}
4216
4217 QString translate(const char *context, const char *sourceText, const char *disambiguation, int n) const override
4218 {
4219 Q_UNUSED(disambiguation);
4220 Q_UNUSED(n);
4221
4222 if (srcTxt == sourceText)
4223 ctxt = QByteArray(context);
4224
4225 return QString(sourceText);
4226 }
4227
4228 bool isEmpty() const override
4229 {
4230 return false;
4231 }
4232
4233 const char *sourceText() const
4234 { return srcTxt.constData(); }
4235
4236 QByteArray context() const
4237 { return ctxt; }
4238
4239private:
4240 QByteArray srcTxt;
4241 mutable QByteArray ctxt;
4242};
4243
4244void tst_QJSEngine::translationFilePath()
4245{
4246 QFETCH(QString, filename);
4247
4248 DummyTranslator translator("some text");
4249 QCoreApplication::installTranslator(messageFile: &translator);
4250 QByteArray scriptContent = QByteArray("qsTr('%1')").replace(before: "%1", after: translator.sourceText());
4251
4252 QJSEngine engine;
4253 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
4254 QJSValue result = engine.evaluate(program: scriptContent, fileName: filename);
4255 QCOMPARE(translator.context(), QByteArray("script"));
4256
4257 QCoreApplication::removeTranslator(messageFile: &translator);
4258}
4259
4260void tst_QJSEngine::installConsoleFunctions()
4261{
4262 QJSEngine engine;
4263 QJSValue global = engine.globalObject();
4264 QVERIFY(global.property("console").isUndefined());
4265 QVERIFY(global.property("print").isUndefined());
4266
4267 engine.installExtensions(extensions: QJSEngine::ConsoleExtension);
4268 QVERIFY(global.property("console").isObject());
4269 QVERIFY(global.property("print").isCallable());
4270}
4271
4272void tst_QJSEngine::logging()
4273{
4274 QLoggingCategory loggingCategory("js");
4275 QVERIFY(loggingCategory.isDebugEnabled());
4276 QVERIFY(loggingCategory.isWarningEnabled());
4277 QVERIFY(loggingCategory.isCriticalEnabled());
4278
4279 QJSEngine engine;
4280 engine.installExtensions(extensions: QJSEngine::ConsoleExtension);
4281
4282 QTest::ignoreMessage(type: QtDebugMsg, message: "console.debug");
4283 engine.evaluate(program: "console.debug('console.debug')");
4284 QTest::ignoreMessage(type: QtDebugMsg, message: "console.log");
4285 engine.evaluate(program: "console.log('console.log')");
4286 QTest::ignoreMessage(type: QtInfoMsg, message: "console.info");
4287 engine.evaluate(program: "console.info('console.info')");
4288 QTest::ignoreMessage(type: QtWarningMsg, message: "console.warn");
4289 engine.evaluate(program: "console.warn('console.warn')");
4290 QTest::ignoreMessage(type: QtCriticalMsg, message: "console.error");
4291 engine.evaluate(program: "console.error('console.error')");
4292
4293 QTest::ignoreMessage(type: QtDebugMsg, message: ": 1");
4294 engine.evaluate(program: "console.count()");
4295
4296 QTest::ignoreMessage(type: QtDebugMsg, message: ": 2");
4297 engine.evaluate(program: "console.count()");
4298}
4299
4300void tst_QJSEngine::tracing()
4301{
4302 QJSEngine engine;
4303 engine.installExtensions(extensions: QJSEngine::ConsoleExtension);
4304
4305 QTest::ignoreMessage(type: QtDebugMsg, message: "%entry (:1)");
4306 engine.evaluate(program: "console.trace()");
4307
4308 QTest::ignoreMessage(type: QtDebugMsg, message: "a (:1)\nb (:1)\nc (:1)\n%entry (:1)");
4309 engine.evaluate(program: "function a() { console.trace(); } function b() { a(); } function c() { b(); }");
4310 engine.evaluate(program: "c()");
4311}
4312
4313void tst_QJSEngine::asserts()
4314{
4315 QJSEngine engine;
4316 engine.installExtensions(extensions: QJSEngine::ConsoleExtension);
4317
4318 QTest::ignoreMessage(type: QtCriticalMsg, message: "This will fail\n%entry (:1)");
4319 engine.evaluate(program: "console.assert(0, 'This will fail')");
4320
4321 QTest::ignoreMessage(type: QtCriticalMsg, message: "This will fail too\n%entry (:1)");
4322 engine.evaluate(program: "console.assert(1 > 2, 'This will fail too')");
4323}
4324
4325void tst_QJSEngine::exceptions()
4326{
4327 QJSEngine engine;
4328 engine.installExtensions(extensions: QJSEngine::ConsoleExtension);
4329
4330 QTest::ignoreMessage(type: QtCriticalMsg, message: "Exception 1\n%entry (:1)");
4331 engine.evaluate(program: "console.exception('Exception 1')");
4332}
4333
4334void tst_QJSEngine::installGarbageCollectionFunctions()
4335{
4336 QJSEngine engine;
4337 QJSValue global = engine.globalObject();
4338 QVERIFY(global.property("gc").isUndefined());
4339
4340 engine.installExtensions(extensions: QJSEngine::GarbageCollectionExtension);
4341 QVERIFY(global.property("gc").isCallable());
4342}
4343
4344void tst_QJSEngine::installAllExtensions()
4345{
4346 QJSEngine engine;
4347 QJSValue global = engine.globalObject();
4348 // Pick out a few properties from each extension and check that they're there.
4349 QVERIFY(global.property("qsTranslate").isUndefined());
4350 QVERIFY(global.property("console").isUndefined());
4351 QVERIFY(global.property("print").isUndefined());
4352 QVERIFY(global.property("gc").isUndefined());
4353
4354 engine.installExtensions(extensions: QJSEngine::AllExtensions);
4355 QVERIFY(global.property("qsTranslate").isCallable());
4356 QVERIFY(global.property("console").isObject());
4357 QVERIFY(global.property("print").isCallable());
4358 QVERIFY(global.property("gc").isCallable());
4359}
4360
4361class ObjectWithPrivateMethods : public QObject
4362{
4363 Q_OBJECT
4364private slots:
4365 void myPrivateMethod() {}
4366};
4367
4368void tst_QJSEngine::privateMethods()
4369{
4370 ObjectWithPrivateMethods object;
4371 QJSEngine engine;
4372 QJSValue jsWrapper = engine.newQObject(object: &object);
4373 QQmlEngine::setObjectOwnership(&object, QQmlEngine::CppOwnership);
4374
4375 QSet<QString> privateMethods;
4376 {
4377 const QMetaObject *mo = object.metaObject();
4378 for (int i = 0; i < mo->methodCount(); ++i) {
4379 const QMetaMethod method = mo->method(index: i);
4380 if (method.access() == QMetaMethod::Private)
4381 privateMethods << QString::fromUtf8(str: method.name());
4382 }
4383 }
4384
4385 QVERIFY(privateMethods.contains("myPrivateMethod"));
4386 QVERIFY(privateMethods.contains("_q_reregisterTimers"));
4387 privateMethods << QStringLiteral("deleteLater") << QStringLiteral("destroyed");
4388
4389 QJSValueIterator it(jsWrapper);
4390 while (it.hasNext()) {
4391 it.next();
4392 QVERIFY(!privateMethods.contains(it.name()));
4393 }
4394}
4395
4396void tst_QJSEngine::engineForObject()
4397{
4398 QObject object;
4399 {
4400 QJSEngine engine;
4401 QVERIFY(!qjsEngine(&object));
4402 QJSValue wrapper = engine.newQObject(object: &object);
4403 QQmlEngine::setObjectOwnership(&object, QQmlEngine::CppOwnership);
4404 QVERIFY(qjsEngine(&object));
4405#ifdef QT_DEPRECATED
4406 QCOMPARE(qjsEngine(&object), wrapper.engine());
4407#endif
4408 }
4409 QVERIFY(!qjsEngine(&object));
4410}
4411
4412void tst_QJSEngine::intConversion_QTBUG43309()
4413{
4414 // This failed in the interpreter:
4415 QJSEngine engine;
4416 QString jsCode = "var n = 0.1; var m = (n*255) | 0; m";
4417 QJSValue result = engine.evaluate( program: jsCode );
4418 QVERIFY(result.isNumber());
4419 QCOMPARE(result.toNumber(), 25.0);
4420}
4421
4422#ifdef QT_DEPRECATED
4423// QTBUG-44039 and QTBUG-43885:
4424void tst_QJSEngine::toFixed()
4425{
4426 QJSEngine engine;
4427 QJSValue result = engine.evaluate(QStringLiteral("(12.5).toFixed()"));
4428 QVERIFY(result.isString());
4429 QCOMPARE(result.toString(), QStringLiteral("13"));
4430 result = engine.evaluate(QStringLiteral("(12.05).toFixed(1)"));
4431 QVERIFY(result.isString());
4432 QCOMPARE(result.toString(), QStringLiteral("12.1"));
4433}
4434#endif
4435
4436void tst_QJSEngine::argumentEvaluationOrder()
4437{
4438 QJSEngine engine;
4439 QJSValue ok = engine.evaluate(
4440 program: "function record(arg1, arg2) {\n"
4441 " parameters = [arg1, arg2]\n"
4442 "}\n"
4443 "function test() {\n"
4444 " var i = 2;\n"
4445 " record(i, i += 2);\n"
4446 "}\n"
4447 "test()\n"
4448 "parameters[0] == 2 && parameters[1] == 4");
4449 qDebug() << ok.toString();
4450 QVERIFY(ok.isBool());
4451 QVERIFY(ok.toBool());
4452
4453}
4454
4455class TestObject : public QObject
4456{
4457 Q_OBJECT
4458public:
4459 TestObject() {}
4460
4461 bool called = false;
4462
4463 Q_INVOKABLE void callMe(QQmlV4Function *) {
4464 called = true;
4465 }
4466};
4467
4468void tst_QJSEngine::v4FunctionWithoutQML()
4469{
4470 TestObject obj;
4471 QJSEngine engine;
4472 QJSValue wrapper = engine.newQObject(object: &obj);
4473 QQmlEngine::setObjectOwnership(&obj, QQmlEngine::CppOwnership);
4474 QVERIFY(!obj.called);
4475 wrapper.property(name: "callMe").call();
4476 QVERIFY(obj.called);
4477}
4478
4479void tst_QJSEngine::withNoContext()
4480{
4481 // Don't crash (QTBUG-53794)
4482 QJSEngine engine;
4483 engine.evaluate(program: "with (noContext) true");
4484}
4485
4486void tst_QJSEngine::holeInPropertyData()
4487{
4488 QJSEngine engine;
4489 QJSValue ok = engine.evaluate(
4490 program: "var o = {};\n"
4491 "o.bar = 0xcccccccc;\n"
4492 "o.foo = 0x55555555;\n"
4493 "Object.defineProperty(o, 'bar', { get: function() { return 0xffffffff }});\n"
4494 "o.bar === 0xffffffff && o.foo === 0x55555555;");
4495 QVERIFY(ok.isBool());
4496 QVERIFY(ok.toBool());
4497}
4498
4499void tst_QJSEngine::basicBlockMergeAfterLoopPeeling()
4500{
4501 QJSEngine engine;
4502 QJSValue ok = engine.evaluate(
4503 program: "function crashMe() {\n"
4504 " var seen = false;\n"
4505 " while (globalVar) {\n"
4506 " if (seen)\n"
4507 " return;\n"
4508 " seen = true;\n"
4509 " }\n"
4510 "}\n");
4511 QVERIFY(!ok.isCallable());
4512
4513}
4514
4515void tst_QJSEngine::modulusCrash()
4516{
4517 QJSEngine engine;
4518 QJSValue result = engine.evaluate(
4519 program: "var a = -2147483648; var b = -1; var c = a % b; c;"
4520 );
4521 QVERIFY(result.isNumber() && result.toNumber() == 0.);
4522}
4523
4524void tst_QJSEngine::malformedExpression()
4525{
4526 QJSEngine engine;
4527 engine.evaluate(program: "5%55555&&5555555\n7-0");
4528}
4529
4530void tst_QJSEngine::scriptScopes()
4531{
4532 QJSEngine engine;
4533
4534 QJSValue def = engine.evaluate(program: "'use strict'; function foo() { return 42 }");
4535 QVERIFY(!def.isError());
4536 QJSValue globalObject = engine.globalObject();
4537 QJSValue foo = globalObject.property(name: "foo");
4538 QVERIFY(foo.isObject());
4539 QVERIFY(foo.isCallable());
4540
4541 QJSValue use = engine.evaluate(program: "'use strict'; foo()");
4542 QVERIFY(use.isNumber());
4543 QCOMPARE(use.toInt(), 42);
4544}
4545
4546void tst_QJSEngine::binaryNumbers()
4547{
4548 QJSEngine engine;
4549
4550 QJSValue result = engine.evaluate(program: "0b1001");
4551 QVERIFY(result.isNumber());
4552 QVERIFY(result.toNumber() == 9);
4553
4554 result = engine.evaluate(program: "0B1001");
4555 QVERIFY(result.isNumber());
4556 QVERIFY(result.toNumber() == 9);
4557
4558 result = engine.evaluate(program: "0b2");
4559 QVERIFY(result.isError());
4560}
4561
4562void tst_QJSEngine::octalNumbers()
4563{
4564 QJSEngine engine;
4565
4566 QJSValue result = engine.evaluate(program: "0o11");
4567 QVERIFY(result.isNumber());
4568 QVERIFY(result.toNumber() == 9);
4569
4570 result = engine.evaluate(program: "0O11");
4571 QVERIFY(result.isNumber());
4572 QVERIFY(result.toNumber() == 9);
4573
4574 result = engine.evaluate(program: "0o9");
4575 QVERIFY(result.isError());
4576}
4577
4578void tst_QJSEngine::incrementAfterNewline()
4579{
4580 QJSEngine engine;
4581
4582 QJSValue result = engine.evaluate(program: "var x = 0; if (\n++x) x; else -x;");
4583 QVERIFY(result.isNumber());
4584 QVERIFY(result.toNumber() == 1);
4585
4586 result = engine.evaluate(program: "var x = 0; if (\n--x) x; else -x;");
4587 QVERIFY(result.isNumber());
4588 QVERIFY(result.toNumber() == -1);
4589}
4590
4591void tst_QJSEngine::deleteInsideForIn()
4592{
4593 QJSEngine engine;
4594
4595 QJSValue iterationCount = engine.evaluate(
4596 program: "var o = { a: 1, b: 2, c: 3, d: 4};\n"
4597 "var count = 0;\n"
4598 "for (var prop in o) { count++; delete o[prop]; }\n"
4599 "count");
4600 QVERIFY(iterationCount.isNumber());
4601 QCOMPARE(iterationCount.toInt(), 4);
4602}
4603
4604void tst_QJSEngine::functionToString_data()
4605{
4606 QTest::addColumn<QString>(name: "source");
4607 QTest::addColumn<QString>(name: "expectedString");
4608
4609 QTest::newRow(dataTag: "named function") << QString::fromLatin1(str: "function f() {}; f.toString()")
4610 << QString::fromLatin1(str: "function f() { [native code] }");
4611 QTest::newRow(dataTag: "anonymous function") << QString::fromLatin1(str: "(function() {}).toString()")
4612 << QString::fromLatin1(str: "function() { [native code] }");
4613}
4614
4615// Tests that function.toString() prints the function's name.
4616void tst_QJSEngine::functionToString()
4617{
4618 QFETCH(QString, source);
4619 QFETCH(QString, expectedString);
4620
4621 QJSEngine engine;
4622 engine.installExtensions(extensions: QJSEngine::AllExtensions);
4623 QJSValue evaluationResult = engine.evaluate(program: source);
4624 QVERIFY(!evaluationResult.isError());
4625 QCOMPARE(evaluationResult.toString(), expectedString);
4626}
4627
4628void tst_QJSEngine::stringReplace()
4629{
4630 QJSEngine engine;
4631
4632 QJSValue val = engine.evaluate(program: "'x'.replace('x', '$1')");
4633 QVERIFY(val.isString());
4634 QCOMPARE(val.toString(), QString("$1"));
4635
4636 val = engine.evaluate(program: "'x'.replace('x', '$10')");
4637 QVERIFY(val.isString());
4638 QCOMPARE(val.toString(), QString("$10"));
4639
4640 val = engine.evaluate(program: "'x'.replace('x', '$01')");
4641 QVERIFY(val.isString());
4642 QCOMPARE(val.toString(), QString("$01"));
4643
4644 val = engine.evaluate(program: "'x'.replace('x', '$0')");
4645 QVERIFY(val.isString());
4646 QCOMPARE(val.toString(), QString("$0"));
4647
4648 val = engine.evaluate(program: "'x'.replace('x', '$00')");
4649 QVERIFY(val.isString());
4650 QCOMPARE(val.toString(), QString("$00"));
4651
4652 val = engine.evaluate(program: "'x'.replace(/(x)/, '$1')");
4653 QVERIFY(val.isString());
4654 QCOMPARE(val.toString(), QString("x"));
4655
4656 val = engine.evaluate(program: "'x'.replace(/(x)/, '$01')");
4657 QVERIFY(val.isString());
4658 QCOMPARE(val.toString(), QString("x"));
4659
4660 val = engine.evaluate(program: "'x'.replace(/(x)/, '$2')");
4661 QVERIFY(val.isString());
4662 QCOMPARE(val.toString(), QString("$2"));
4663
4664 val = engine.evaluate(program: "'x'.replace(/(x)/, '$02')");
4665 QVERIFY(val.isString());
4666 QCOMPARE(val.toString(), QString("$02"));
4667
4668 val = engine.evaluate(program: "'x'.replace(/(x)/, '$0')");
4669 QVERIFY(val.isString());
4670 QCOMPARE(val.toString(), QString("$0"));
4671
4672 val = engine.evaluate(program: "'x'.replace(/(x)/, '$00')");
4673 QVERIFY(val.isString());
4674 QCOMPARE(val.toString(), QString("$00"));
4675
4676 val = engine.evaluate(program: "'x'.replace(/()()()()()()()()()(x)/, '$11')");
4677 QVERIFY(val.isString());
4678 QCOMPARE(val.toString(), QString("1"));
4679
4680 val = engine.evaluate(program: "'x'.replace(/()()()()()()()()()(x)/, '$10')");
4681 QVERIFY(val.isString());
4682 QCOMPARE(val.toString(), QString("x"));
4683
4684 val = engine.evaluate(program: "'123'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')");
4685 QVERIFY(val.isString());
4686 QCOMPARE(val.toString(), QString("123"));
4687
4688 val = engine.evaluate(program: "'123.00'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')");
4689 QVERIFY(val.isString());
4690 QCOMPARE(val.toString(), QString("123"));
4691
4692 val = engine.evaluate(program: "'123.0'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')");
4693 QVERIFY(val.isString());
4694 QCOMPARE(val.toString(), QString("123"));
4695
4696 val = engine.evaluate(program: "'123.'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')");
4697 QVERIFY(val.isString());
4698 QCOMPARE(val.toString(), QString("123"));
4699
4700 val = engine.evaluate(program: "'123.50'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')");
4701 QVERIFY(val.isString());
4702 QCOMPARE(val.toString(), QString("123.5"));
4703
4704 val = engine.evaluate(program: "'123.05'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')");
4705 QVERIFY(val.isString());
4706 QCOMPARE(val.toString(), QString("123.05"));
4707
4708 val = engine.evaluate(program: "'0.050'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')");
4709 QVERIFY(val.isString());
4710 QCOMPARE(val.toString(), QString("0.05"));
4711}
4712
4713void tst_QJSEngine::protoChanges_QTBUG68369()
4714{
4715 QJSEngine engine;
4716 QJSValue ok = engine.evaluate(
4717 program: "var o = { x: true };"
4718 "var p1 = {};"
4719 "var p2 = {};"
4720 "o.__proto__ = p1;"
4721 "o.__proto__ = p2;"
4722 "o.__proto__ = p1;"
4723 "p1.y = true;"
4724 "o.y"
4725 );
4726 QVERIFY(ok.toBool() == true);
4727}
4728
4729void tst_QJSEngine::multilineStrings()
4730{
4731 QJSEngine engine;
4732 QJSValue result = engine.evaluate(
4733 program: "var x = `a\nb`; x;"
4734 );
4735 QVERIFY(result.isString());
4736 QVERIFY(result.toString() == QStringLiteral("a\nb"));
4737
4738}
4739
4740void tst_QJSEngine::throwError()
4741{
4742 QJSEngine engine;
4743 QJSValue wrappedThis = engine.newQObject(object: this);
4744 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
4745 engine.globalObject().setProperty(name: "testCase", value: wrappedThis);
4746
4747 QJSValue result = engine.evaluate(
4748 program: "function test(){\n"
4749 "try {\n"
4750 " return testCase.throwingCppMethod1();\n"
4751 "} catch (error) {\n"
4752 " return error;\n"
4753 "}\n"
4754 "return \"not reached!\";\n"
4755 "}\n"
4756 "test();"
4757 );
4758 QVERIFY(result.isError());
4759 QCOMPARE(result.errorType(), QJSValue::GenericError);
4760 QCOMPARE(result.property("lineNumber").toString(), "3");
4761 QCOMPARE(result.property("message").toString(), "blub");
4762 QVERIFY(!result.property("stack").isUndefined());
4763}
4764
4765void tst_QJSEngine::throwErrorObject()
4766{
4767 QJSEngine engine;
4768 QJSValue wrappedThis = engine.newQObject(object: this);
4769 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
4770 engine.globalObject().setProperty(name: "testCase", value: wrappedThis);
4771
4772 QJSValue result = engine.evaluate(
4773 program: "function test(){\n"
4774 " try {\n"
4775 " testCase.throwingCppMethod2();\n"
4776 " } catch (error) {\n"
4777 " if (error instanceof TypeError) {\n"
4778 " return error;\n"
4779 " }\n"
4780 " }\n"
4781 " return null;\n"
4782 "}\n"
4783 "test();"
4784 );
4785 QVERIFY(result.isError());
4786 QCOMPARE(result.errorType(), QJSValue::TypeError);
4787 QCOMPARE(result.property("lineNumber").toString(), "3");
4788 QCOMPARE(result.property("message").toString(), "Wrong type");
4789 QVERIFY(!result.property("stack").isUndefined());
4790}
4791
4792void tst_QJSEngine::returnError()
4793{
4794 QJSEngine engine;
4795 QJSValue wrappedThis = engine.newQObject(object: this);
4796 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
4797 engine.globalObject().setProperty(name: "testCase", value: wrappedThis);
4798
4799 QJSValue result = engine.evaluate(program: "testCase.throwingCppMethod3()");
4800 QVERIFY(result.isError());
4801 QCOMPARE(result.errorType(), QJSValue::EvalError);
4802 QCOMPARE(result.property("lineNumber").toString(), "1");
4803 QCOMPARE(result.property("message").toString(), "Something is wrong");
4804 QVERIFY(!result.property("stack").isUndefined());
4805}
4806
4807QJSValue tst_QJSEngine::throwingCppMethod1()
4808{
4809 qjsEngine(this)->throwError(message: "blub");
4810 return QJSValue(47);
4811}
4812
4813void tst_QJSEngine::throwingCppMethod2()
4814{
4815 qjsEngine(this)->throwError(errorType: QJSValue::TypeError, message: "Wrong type");
4816}
4817
4818QJSValue tst_QJSEngine::throwingCppMethod3()
4819{
4820 return qjsEngine(this)->newErrorObject(errorType: QJSValue::EvalError, message: "Something is wrong");
4821}
4822
4823void tst_QJSEngine::mathMinMax()
4824{
4825 QJSEngine engine;
4826
4827 QJSValue result = engine.evaluate(program: "var a = .5; Math.min(1, 2, 3.5 + a, '5')");
4828 QCOMPARE(result.toNumber(), 1.0);
4829 QVERIFY(QJSValuePrivate::getValue(&result) != nullptr);
4830 QVERIFY(QJSValuePrivate::getValue(&result)->isInteger());
4831
4832 result = engine.evaluate(program: "var a = .5; Math.max('0', 1, 2, 3.5 + a)");
4833 QCOMPARE(result.toNumber(), 4.0);
4834 QVERIFY(QJSValuePrivate::getValue(&result) != nullptr);
4835 QVERIFY(QJSValuePrivate::getValue(&result)->isInteger());
4836}
4837
4838void tst_QJSEngine::importModule()
4839{
4840 // This is just a basic test for the API. Primary test coverage is via the ES test suite.
4841 QJSEngine engine;
4842 QJSValue ns = engine.importModule(QStringLiteral(":/testmodule.mjs"));
4843 QCOMPARE(ns.property("value").toInt(), 42);
4844 ns.property(name: "sideEffect").call();
4845
4846 // Make sure that importing a second time will return the same instance.
4847 QJSValue secondNamespace = engine.importModule(QStringLiteral(":/testmodule.mjs"));
4848 QCOMPARE(secondNamespace.property("value").toInt(), 43);
4849}
4850
4851void tst_QJSEngine::importModuleRelative()
4852{
4853 const QString oldWorkingDirectory = QDir::currentPath();
4854 auto workingDirectoryGuard = qScopeGuard(f: [oldWorkingDirectory]{
4855 QDir::setCurrent(oldWorkingDirectory);
4856 });
4857
4858 QTemporaryDir tempDir;
4859 QVERIFY(tempDir.isValid());
4860 QDir::setCurrent(tempDir.path());
4861
4862 {
4863 QFile f(QStringLiteral("relativemodule.mjs"));
4864 QVERIFY(f.open(QIODevice::WriteOnly|QIODevice::Truncate));
4865 f.write(QByteArrayLiteral("var value = 100; export { value }; export function change() { value = value + 1 }"));
4866 }
4867
4868 QJSEngine engine;
4869
4870 {
4871 QJSValue module = engine.importModule(QStringLiteral("relativemodule.mjs"));
4872 QVERIFY2(!module.isError(), qPrintable(module.toString()));
4873 QCOMPARE(module.property("value").toInt(), 100);
4874
4875 module.property(name: "change").call();
4876 }
4877
4878 {
4879 QJSValue sameModule = engine.importModule(fileName: tempDir.filePath(QStringLiteral("relativemodule.mjs")));
4880 QVERIFY2(!sameModule.isError(), qPrintable(sameModule.toString()));
4881 QCOMPARE(sameModule.property("value").toInt(), 101);
4882 }
4883}
4884
4885void tst_QJSEngine::importModuleWithLexicallyScopedVars()
4886{
4887 QJSEngine engine;
4888 QJSValue ns = engine.importModule(QStringLiteral(":/modulewithlexicals.mjs"));
4889 QVERIFY2(!ns.isError(), qPrintable(ns.toString()));
4890 QCOMPARE(ns.property("main").call().toInt(), 10);
4891}
4892
4893void tst_QJSEngine::importExportErrors()
4894{
4895 {
4896 QJSEngine engine;
4897 QJSValue result = engine.importModule(QStringLiteral(":/importerror1.mjs"));
4898 QVERIFY(result.isError());
4899 QCOMPARE(result.property("lineNumber").toInt(), 2);
4900 }
4901 {
4902 QJSEngine engine;
4903 QJSValue result = engine.importModule(QStringLiteral(":/exporterror1.mjs"));
4904 QVERIFY(result.isError());
4905 QCOMPARE(result.property("lineNumber").toInt(), 2);
4906 }
4907}
4908
4909void tst_QJSEngine::equality()
4910{
4911 QJSEngine engine;
4912 QJSValue ok = engine.evaluate(program: "(0 < 0) ? 'ko' : 'ok'");
4913 QVERIFY(ok.isString());
4914 QCOMPARE(ok.toString(), QString("ok"));
4915}
4916
4917void tst_QJSEngine::aggressiveGc()
4918{
4919 const QByteArray origAggressiveGc = qgetenv(varName: "QV4_MM_AGGRESSIVE_GC");
4920 qputenv(varName: "QV4_MM_AGGRESSIVE_GC", value: "true");
4921 {
4922 QJSEngine engine; // ctor crashes if core allocation methods don't properly scope things.
4923 QJSValue obj = engine.newObject();
4924 QVERIFY(obj.isObject());
4925 }
4926 qputenv(varName: "QV4_MM_AGGRESSIVE_GC", value: origAggressiveGc);
4927}
4928
4929void tst_QJSEngine::noAccumulatorInTemplateLiteral()
4930{
4931 const QByteArray origAggressiveGc = qgetenv(varName: "QV4_MM_AGGRESSIVE_GC");
4932 qputenv(varName: "QV4_MM_AGGRESSIVE_GC", value: "true");
4933 {
4934 QJSEngine engine;
4935
4936 // getTemplateLiteral should not save the accumulator as it's garbage and trashes
4937 // the next GC run. Instead, we want to see the stack overflow error.
4938 QJSValue value = engine.evaluate(program: "function a(){\nS=o=>s\nFunction``\na()}a()");
4939
4940 QVERIFY(value.isError());
4941 QCOMPARE(value.toString(), "RangeError: Maximum call stack size exceeded.");
4942 }
4943 qputenv(varName: "QV4_MM_AGGRESSIVE_GC", value: origAggressiveGc);
4944}
4945
4946void tst_QJSEngine::interrupt_data()
4947{
4948 QTest::addColumn<int>(name: "jitThreshold");
4949 QTest::addColumn<QString>(name: "code");
4950
4951 const int big = (1 << 24);
4952 for (int i = 0; i <= big; i += big) {
4953 const char *mode = i ? "interpret" : "jit";
4954 QTest::addRow(format: "for with content / %s", mode) << i << "var a = 0; for (;;) { a += 2; }";
4955 QTest::addRow(format: "for empty / %s", mode) << i << "for (;;) {}";
4956 QTest::addRow(format: "for continue / %s", mode) << i << "for (;;) { continue; }";
4957 QTest::addRow(format: "while with content / %s", mode) << i << "var a = 0; while (true) { a += 2; }";
4958 QTest::addRow(format: "while empty / %s", mode) << i << "while (true) {}";
4959 QTest::addRow(format: "while continue / %s", mode) << i << "while (true) { continue; }";
4960 QTest::addRow(format: "do with content / %s", mode) << i << "var a = 0; do { a += 2; } while (true);";
4961 QTest::addRow(format: "do empty / %s", mode) << i << "do {} while (true);";
4962 QTest::addRow(format: "do continue / %s", mode) << i << "do { continue; } while (true);";
4963 QTest::addRow(format: "nested loops / %s", mode) << i << "while (true) { for (;;) {} }";
4964 QTest::addRow(format: "labeled continue / %s", mode) << i << "a: while (true) { for (;;) { continue a; } }";
4965 QTest::addRow(format: "labeled break / %s", mode) << i << "while (true) { a: for (;;) { break a; } }";
4966 QTest::addRow(format: "tail call / %s", mode) << i << "'use strict';\nfunction x() { return x(); }; x();";
4967 QTest::addRow(format: "huge array join / %s", mode) << i << "Array(1E9)|1";
4968 }
4969}
4970
4971class TemporaryJitThreshold
4972{
4973 Q_DISABLE_COPY_MOVE(TemporaryJitThreshold)
4974public:
4975 TemporaryJitThreshold(int threshold) {
4976 m_wasSet = qEnvironmentVariableIsSet(varName: m_envVar);
4977 m_value = qgetenv(varName: m_envVar);
4978 qputenv(varName: m_envVar, value: QByteArray::number(threshold));
4979 }
4980
4981 ~TemporaryJitThreshold()
4982 {
4983 if (m_wasSet)
4984 qputenv(varName: m_envVar, value: m_value);
4985 else
4986 qunsetenv(varName: m_envVar);
4987 }
4988
4989private:
4990 const char *m_envVar = "QV4_JIT_CALL_THRESHOLD";
4991 bool m_wasSet = false;
4992 QByteArray m_value;
4993};
4994
4995void tst_QJSEngine::interrupt()
4996{
4997#if QT_CONFIG(cxx11_future)
4998 QFETCH(int, jitThreshold);
4999 QFETCH(QString, code);
5000
5001 TemporaryJitThreshold threshold(jitThreshold);
5002 Q_UNUSED(threshold);
5003
5004 QJSEngine *engineInThread = nullptr;
5005 QScopedPointer<QThread> worker(QThread::create(f: [&engineInThread, &code, jitThreshold](){
5006 QJSEngine jsEngine;
5007 engineInThread = &jsEngine;
5008 QJSValue result = jsEngine.evaluate(program: code);
5009 QVERIFY(jsEngine.isInterrupted());
5010 QVERIFY(result.isError());
5011 QCOMPARE(result.toString(), QString::fromLatin1("Error: Interrupted"));
5012 engineInThread = nullptr;
5013 }));
5014 worker->start();
5015
5016 QTRY_VERIFY(engineInThread);
5017
5018 engineInThread->setInterrupted(true);
5019
5020 QVERIFY(worker->wait());
5021 QVERIFY(!engineInThread);
5022#else
5023 QSKIP("This test requires C++11 futures");
5024#endif
5025}
5026
5027void tst_QJSEngine::triggerBackwardJumpWithDestructuring()
5028{
5029 QJSEngine engine;
5030 auto value = engine.evaluate(
5031 program: "function makeArray(n) { return [...Array(n).keys()]; }\n"
5032 "for (let i=0;i<100;++i) {\n"
5033 " let arr = makeArray(20)\n"
5034 " arr.sort( (a, b) => b - a )\n"
5035 "}"
5036 );
5037 QVERIFY(!value.isError());
5038}
5039
5040void tst_QJSEngine::arrayConcatOnSparseArray()
5041{
5042 QJSEngine engine;
5043 engine.installExtensions(extensions: QJSEngine::GarbageCollectionExtension);
5044 const auto value = engine.evaluate(
5045 program: "(function() {\n"
5046 " const v4 = [1,2,3];\n"
5047 " const v7 = [4,5];\n"
5048 " v7.length = 1337;\n"
5049 " const v9 = v4.concat(v7);\n"
5050 " gc();\n"
5051 " return v9;\n"
5052 "})();");
5053 QCOMPARE(value.property("length").toInt(), 1340);
5054 for (int i = 0; i < 5; ++i)
5055 QCOMPARE(value.property(i).toInt(), i + 1);
5056 for (int i = 5; i < 1340; ++i)
5057 QVERIFY(value.property(i).isUndefined());
5058}
5059
5060void tst_QJSEngine::sortSparseArray()
5061{
5062 QJSEngine engine;
5063 engine.installExtensions(extensions: QJSEngine::ConsoleExtension);
5064 const auto value = engine.evaluate(
5065 program: "(function() {\n"
5066 " var sparse = [0];\n"
5067 " sparse = Object.defineProperty(sparse, \"10\", "
5068 " {get: ()=>{return 2}, set: ()=>{return 2}} );\n"
5069 " return Array.prototype.sort.call(sparse, ()=>{});\n"
5070 "})();");
5071
5072 QCOMPARE(value.property("length").toInt(), 11);
5073 QVERIFY(value.property(0).isNumber());
5074 QCOMPARE(value.property(0).toInt(), 0);
5075 QVERIFY(value.property(1).isNumber());
5076 QCOMPARE(value.property(1).toInt(), 2);
5077 QVERIFY(value.property(10).isUndefined());
5078}
5079
5080void tst_QJSEngine::compileBrokenRegexp()
5081{
5082 QJSEngine engine;
5083 const auto value = engine.evaluate(
5084 program: "(function() {"
5085 "var ret = new RegExp(Array(4097).join("
5086 " String.fromCharCode(58)) + Array(4097).join(String.fromCharCode(480)) "
5087 " + Array(65537).join(String.fromCharCode(5307)));"
5088 "return RegExp.prototype.compile.call(ret, 'a','b');"
5089 "})();"
5090 );
5091
5092 QVERIFY(value.isError());
5093 QCOMPARE(value.toString(), "SyntaxError: Invalid flags supplied to RegExp constructor");
5094}
5095
5096void tst_QJSEngine::tostringRecursionCheck()
5097{
5098 QJSEngine engine;
5099 auto value = engine.evaluate(program: R"js(
5100 var a = {};
5101 var b = new Array(1337);
5102 function main() {
5103 var ret = a.toLocaleString;
5104 b[1] = ret;
5105 Array = {};
5106 Object.toString = b[1];
5107 var ret = String.prototype.lastIndexOf.call({}, b[1]);
5108 var ret = String.prototype.charAt.call(Function, Object);
5109 }
5110 main();
5111 )js");
5112 QVERIFY(value.isError());
5113 QCOMPARE(value.toString(), QLatin1String("RangeError: Maximum call stack size exceeded."));
5114}
5115
5116void tst_QJSEngine::arrayIncludesWithLargeArray()
5117{
5118 QJSEngine engine;
5119 auto value = engine.evaluate(program: R"js(
5120 let arr = new Array(10000000)
5121 arr.includes(42)
5122 )js");
5123 QVERIFY(value.isBool());
5124 QCOMPARE(value.toBool(), false);
5125}
5126
5127void tst_QJSEngine::printCircularArray()
5128{
5129 QJSEngine engine;
5130 engine.installExtensions(extensions: QJSEngine::ConsoleExtension);
5131 QTest::ignoreMessage(type: QtMsgType::QtDebugMsg, message: "[[Circular]]");
5132 auto value = engine.evaluate(program: R"js(
5133 let v1 = []
5134 v1.push(v1)
5135 console.log(v1)
5136 )js");
5137}
5138
5139void tst_QJSEngine::sortNonStringArray()
5140{
5141 QJSEngine engine;
5142 const auto value = engine.evaluate(
5143 program: "const v4 = [Symbol.iterator, 1];"
5144 "const v5 = v4.sort();"
5145 );
5146 QVERIFY(value.isError());
5147 QCOMPARE(value.toString(), "TypeError: Cannot convert a symbol to a string.");
5148}
5149
5150void tst_QJSEngine::iterateInvalidProxy()
5151{
5152 QJSEngine engine;
5153 const auto value = engine.evaluate(
5154 program: "const v1 = new Proxy(Reflect, Reflect);"
5155 "for (const v2 in v1) {}"
5156 "const v3 = { getOwnPropertyDescriptor: eval, getPrototypeOf: eval };"
5157 "const v4 = new Proxy(v3, v3);"
5158 "for (const v5 in v4) {}"
5159 );
5160 QVERIFY(value.isError());
5161 QCOMPARE(value.toString(), "TypeError: Type error");
5162}
5163
5164void tst_QJSEngine::applyOnHugeArray()
5165{
5166 QJSEngine engine;
5167 const auto value = engine.evaluate(
5168 program: "var a = new Array(10);"
5169 "a[536870912] = Function;"
5170 "Function.apply('aaaaaaaa', a);"
5171 );
5172 QVERIFY(value.isError());
5173 QCOMPARE(value.toString(), "RangeError: Array too large for apply().");
5174}
5175
5176void tst_QJSEngine::typedArraySet()
5177{
5178 QJSEngine engine;
5179 const auto value = engine.evaluate(
5180 program: "(function() {"
5181 " var length = 0xffffffe;"
5182 " var offset = 0xfffffff0;"
5183 " var e1;"
5184 " var e2;"
5185 " try {"
5186 " var source1 = new Int8Array(length);"
5187 " var target1 = new Int8Array(length);"
5188 " target1.set(source1, offset);"
5189 " } catch (intError) {"
5190 " e1 = intError;"
5191 " }"
5192 " try {"
5193 " var source2 = new Array(length);"
5194 " var target2 = new Int8Array(length);"
5195 " target2.set(source2, offset);"
5196 " } catch (arrayError) {"
5197 " e2 = arrayError;"
5198 " }"
5199 " return [e1, e2];"
5200 "})();"
5201 );
5202
5203 QVERIFY(value.isArray());
5204 for (int i = 0; i < 2; ++i) {
5205 const auto error = value.property(arrayIndex: i);
5206 QVERIFY(error.isError());
5207 QCOMPARE(error.toString(), "RangeError: TypedArray.set: out of range");
5208 }
5209}
5210
5211void tst_QJSEngine::dataViewCtor()
5212{
5213 QJSEngine engine;
5214 const auto error = engine.evaluate(program: R"(
5215 (function() { try {
5216 var buf = new ArrayBuffer(0x200);
5217 var vuln = new DataView(buf, 8, 0xfffffff8);
5218 } catch (e) {
5219 return e;
5220 }})()
5221 )");
5222 QVERIFY(error.isError());
5223 QCOMPARE(error.toString(), "RangeError: DataView: constructor arguments out of range");
5224}
5225
5226void tst_QJSEngine::uiLanguage()
5227{
5228 {
5229 QJSEngine engine;
5230
5231 QVERIFY(!engine.globalObject().hasProperty("Qt"));
5232
5233 engine.installExtensions(extensions: QJSEngine::TranslationExtension);
5234 QVERIFY(engine.globalObject().hasProperty("Qt"));
5235 QVERIFY(engine.globalObject().property("Qt").hasProperty("uiLanguage"));
5236
5237 engine.setUiLanguage("Blah");
5238 QCOMPARE(engine.globalObject().property("Qt").property("uiLanguage").toString(), "Blah");
5239
5240 engine.evaluate(program: "Qt.uiLanguage = \"another\"");
5241 QCOMPARE(engine.globalObject().property("Qt").property("uiLanguage").toString(), "another");
5242 }
5243
5244 {
5245 QQmlEngine qmlEngine;
5246 QVERIFY(qmlEngine.globalObject().hasProperty("Qt"));
5247 QVERIFY(qmlEngine.globalObject().property("Qt").hasProperty("uiLanguage"));
5248 qmlEngine.setUiLanguage("Blah");
5249 QCOMPARE(qmlEngine.globalObject().property("Qt").property("uiLanguage").toString(), "Blah");
5250 }
5251}
5252
5253QTEST_MAIN(tst_QJSEngine)
5254
5255#include "tst_qjsengine.moc"
5256
5257

source code of qtdeclarative/tests/auto/qml/qjsengine/tst_qjsengine.cpp