/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/


#include <QtTest/QtTest>

#include <qscriptengine.h>
#include <qscriptengineagent.h>
#include <qscriptprogram.h>
#include <qscriptvalueiterator.h>
#include <qgraphicsitem.h>
#include <qstandarditemmodel.h>
#include <QtCore/qnumeric.h>
#include <stdlib.h>

#include <QtScript/private/qscriptdeclarativeclass_p.h>

#include "../shared/util.h"

Q_DECLARE_METATYPE(QList<int>)
Q_DECLARE_METATYPE(QObjectList)
Q_DECLARE_METATYPE(QScriptProgram)

class tst_QScriptEngine : public QObject
{
    Q_OBJECT

public:
    tst_QScriptEngine();
    virtual ~tst_QScriptEngine();

private slots:
    void constructWithParent();
    void currentContext();
    void pushPopContext();
    void getSetDefaultPrototype_int();
    void getSetDefaultPrototype_customType();
    void newFunction();
    void newFunctionWithArg();
    void newFunctionWithProto();
    void newObject();
    void newArray();
    void newArray_HooliganTask218092();
    void newArray_HooliganTask233836();
    void newVariant();
    void newVariant_defaultPrototype();
    void newVariant_promoteObject();
    void newVariant_replaceValue();
    void newVariant_valueOfToString();
    void newVariant_promoteNonObject();
    void newVariant_promoteNonQScriptObject();
    void newRegExp();
    void jsRegExp();
    void newDate();
    void jsParseDate();
    void newQObject();
    void newQObject_ownership();
    void newQObject_promoteObject();
    void newQObject_sameQObject();
    void newQObject_defaultPrototype();
    void newQObject_promoteNonObject();
    void newQObject_promoteNonQScriptObject();
    void newQMetaObject();
    void newActivationObject();
    void getSetGlobalObject();
    void globalObjectProperties();
    void globalObjectProperties_enumerate();
    void createGlobalObjectProperty();
    void globalObjectGetterSetterProperty();
    void customGlobalObjectWithPrototype();
    void globalObjectWithCustomPrototype();
    void builtinFunctionNames_data();
    void builtinFunctionNames();
    void checkSyntax_data();
    void checkSyntax();
    void canEvaluate_data();
    void canEvaluate();
    void evaluate_data();
    void evaluate();
    void nestedEvaluate();
    void uncaughtException();
    void errorMessage_QT679();
    void valueConversion_basic();
    void valueConversion_customType();
    void valueConversion_sequence();
    void valueConversion_QVariant();
    void valueConversion_hooliganTask248802();
    void valueConversion_basic2();
    void valueConversion_dateTime();
    void valueConversion_regExp();
    void valueConversion_long();
    void qScriptValueFromValue_noEngine();
    void importExtension();
    void infiniteRecursion();
    void castWithPrototypeChain();
    void castWithMultipleInheritance();
    void collectGarbage();
    void reportAdditionalMemoryCost();
    void gcWithNestedDataStructure();
    void processEventsWhileRunning();
    void throwErrorFromProcessEvents_data();
    void throwErrorFromProcessEvents();
    void disableProcessEventsInterval();
    void stacktrace();
    void stacktrace_callJSFromCpp_data();
    void stacktrace_callJSFromCpp();
    void numberParsing_data();
    void numberParsing();
    void automaticSemicolonInsertion();
    void abortEvaluation_notEvaluating();
    void abortEvaluation_data();
    void abortEvaluation();
    void abortEvaluation_tryCatch();
    void abortEvaluation_fromNative();
    void abortEvaluation_QTBUG9433();
    void isEvaluating_notEvaluating();
    void isEvaluating_fromNative();
    void isEvaluating_fromEvent();
    void printFunctionWithCustomHandler();
    void printThrowsException();
    void errorConstructors();
    void argumentsProperty_globalContext();
    void argumentsProperty_JS();
    void argumentsProperty_evaluateInNativeFunction();
    void jsNumberClass();
    void jsForInStatement_simple();
    void jsForInStatement_prototypeProperties();
    void jsForInStatement_mutateWhileIterating();
    void jsForInStatement_arrays();
    void jsForInStatement_nullAndUndefined();
    void jsFunctionDeclarationAsStatement();
    void stringObjects();
    void jsStringPrototypeReplaceBugs();
    void getterSetterThisObject_global();
    void getterSetterThisObject_plain();
    void getterSetterThisObject_prototypeChain();
    void getterSetterThisObject_activation();
    void jsContinueInSwitch();
    void jsShadowReadOnlyPrototypeProperty();
    void toObject();
    void jsReservedWords_data();
    void jsReservedWords();
    void jsFutureReservedWords_data();
    void jsFutureReservedWords();
    void jsThrowInsideWithStatement();
    void getSetAgent_ownership();
    void getSetAgent_deleteAgent();
    void getSetAgent_differentEngine();
    void reentrancy_stringHandles();
    void reentrancy_processEventsInterval();
    void reentrancy_typeConversion();
    void reentrancy_globalObjectProperties();
    void reentrancy_Array();
    void reentrancy_objectCreation();
    void jsIncDecNonObjectProperty();
    void installTranslatorFunctions_data();
    void installTranslatorFunctions();
    void translateScript_data();
    void translateScript();
    void translateScript_crossScript();
    void translateScript_callQsTrFromNative();
    void translateScript_trNoOp();
    void translateScript_callQsTrFromCpp();
    void translateWithInvalidArgs_data();
    void translateWithInvalidArgs();
    void translationContext_data();
    void translationContext();
    void translateScriptIdBased();
    void translateScriptUnicode_data();
    void translateScriptUnicode();
    void translateScriptUnicodeIdBased_data();
    void translateScriptUnicodeIdBased();
    void translateFromBuiltinCallback();
    void functionScopes();
    void nativeFunctionScopes();
    void evaluateProgram();
    void evaluateProgram_customScope();
    void evaluateProgram_closure();
    void evaluateProgram_executeLater();
    void evaluateProgram_multipleEngines();
    void evaluateProgram_empty();
    void collectGarbageAfterConnect();
    void collectGarbageAfterNativeArguments();
    void promoteThisObjectToQObjectInConstructor();
    void scriptValueFromQMetaObject();

    void qRegExpInport_data();
    void qRegExpInport();
    void reentrency();
    void newFixedStaticScopeObject();
    void newGrowingStaticScopeObject();
    void dateRoundtripJSQtJS();
    void dateRoundtripQtJSQt();
    void dateConversionJSQt();
    void dateConversionQtJS();
    void stringListFromArrayWithEmptyElement();
    void collectQObjectWithCachedWrapper_data();
    void collectQObjectWithCachedWrapper();
    void pushContext_noInheritedScope();
};

tst_QScriptEngine::tst_QScriptEngine()
{
}

tst_QScriptEngine::~tst_QScriptEngine()
{
}

void tst_QScriptEngine::constructWithParent()
{
    QPointer<QScriptEngine> ptr;
    {
        QObject obj;
        QScriptEngine *engine = new QScriptEngine(&obj);
        ptr = engine;
    }
    QVERIFY(ptr == 0);
}

void tst_QScriptEngine::currentContext()
{
    QScriptEngine eng;
    QScriptContext *globalCtx = eng.currentContext();
    QVERIFY(globalCtx != 0);
    QVERIFY(globalCtx->parentContext() == 0);
    QCOMPARE(globalCtx->engine(), &eng);
    QCOMPARE(globalCtx->argumentCount(), 0);
    QCOMPARE(globalCtx->backtrace().size(), 1);
    QVERIFY(!globalCtx->isCalledAsConstructor());
    QVERIFY(!globalCtx->callee().isValid());
    QCOMPARE(globalCtx->state(), QScriptContext::NormalState);
    QVERIFY(globalCtx->thisObject().strictlyEquals(eng.globalObject()));
    QVERIFY(globalCtx->activationObject().strictlyEquals(eng.globalObject()));
    QVERIFY(globalCtx->argumentsObject().isObject());
}

void tst_QScriptEngine::pushPopContext()
{
    QScriptEngine eng;
    QScriptContext *globalCtx = eng.currentContext();
    QScriptContext *ctx = eng.pushContext();
    QVERIFY(ctx != 0);
    QCOMPARE(ctx->parentContext(), globalCtx);
    QVERIFY(!ctx->isCalledAsConstructor());
    QVERIFY(!ctx->callee().isValid());
    QVERIFY(ctx->thisObject().strictlyEquals(eng.globalObject()));
    QCOMPARE(ctx->argumentCount(), 0);
    QCOMPARE(ctx->backtrace().size(), 2);
    QCOMPARE(ctx->engine(), &eng);
    QCOMPARE(ctx->state(), QScriptContext::NormalState);
    QVERIFY(ctx->activationObject().isObject());
    QVERIFY(ctx->argumentsObject().isObject());

    QScriptContext *ctx2 = eng.pushContext();
    QVERIFY(ctx2 != 0);
    QCOMPARE(ctx2->parentContext(), ctx);
    QVERIFY(!ctx2->activationObject().strictlyEquals(ctx->activationObject()));
    QVERIFY(!ctx2->argumentsObject().strictlyEquals(ctx->argumentsObject()));

    eng.popContext();
    eng.popContext();
    QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::popContext() doesn't match with pushContext()");
    eng.popContext(); // ignored
    QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::popContext() doesn't match with pushContext()");
    eng.popContext(); // ignored
}

static QScriptValue myFunction(QScriptContext *, QScriptEngine *eng)
{
    return eng->nullValue();
}

static QScriptValue myFunctionWithVoidArg(QScriptContext *, QScriptEngine *eng, void *)
{
    return eng->nullValue();
}

static QScriptValue myThrowingFunction(QScriptContext *ctx, QScriptEngine *)
{
    return ctx->throwError("foo");
}

void tst_QScriptEngine::newFunction()
{
    QScriptEngine eng;
    {
        QScriptValue fun = eng.newFunction(myFunction);
        QCOMPARE(fun.isValid(), true);
        QCOMPARE(fun.isFunction(), true);
        QCOMPARE(fun.isObject(), true);
        QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
        // a prototype property is automatically constructed
        {
            QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
            QVERIFY(prot.isObject());
            QVERIFY(prot.property("constructor").strictlyEquals(fun));
            QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
            QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
        }
        // prototype should be Function.prototype
        QCOMPARE(fun.prototype().isValid(), true);
        QCOMPARE(fun.prototype().isFunction(), true);
        QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);

        QCOMPARE(fun.call().isNull(), true);
        QCOMPARE(fun.construct().isObject(), true);
    }
}

void tst_QScriptEngine::newFunctionWithArg()
{
    QScriptEngine eng;
    {
        QScriptValue fun = eng.newFunction(myFunctionWithVoidArg, (void*)this);
        QVERIFY(fun.isFunction());
        QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
        // a prototype property is automatically constructed
        {
            QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
            QVERIFY(prot.isObject());
            QVERIFY(prot.property("constructor").strictlyEquals(fun));
            QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
            QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
        }
        // prototype should be Function.prototype
        QCOMPARE(fun.prototype().isValid(), true);
        QCOMPARE(fun.prototype().isFunction(), true);
        QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);

        QCOMPARE(fun.call().isNull(), true);
        QCOMPARE(fun.construct().isObject(), true);
    }
}

void tst_QScriptEngine::newFunctionWithProto()
{
    QScriptEngine eng;
    {
        QScriptValue proto = eng.newObject();
        QScriptValue fun = eng.newFunction(myFunction, proto);
        QCOMPARE(fun.isValid(), true);
        QCOMPARE(fun.isFunction(), true);
        QCOMPARE(fun.isObject(), true);
        // internal prototype should be Function.prototype
        QCOMPARE(fun.prototype().isValid(), true);
        QCOMPARE(fun.prototype().isFunction(), true);
        QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
        // public prototype should be the one we passed
        QCOMPARE(fun.property("prototype").strictlyEquals(proto), true);
        QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
        QCOMPARE(proto.property("constructor").strictlyEquals(fun), true);
        QCOMPARE(proto.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);

        QCOMPARE(fun.call().isNull(), true);
        QCOMPARE(fun.construct().isObject(), true);
    }
}

void tst_QScriptEngine::newObject()
{
    QScriptEngine eng;
    QScriptValue object = eng.newObject();
    QCOMPARE(object.isValid(), true);
    QCOMPARE(object.isObject(), true);
    QCOMPARE(object.isFunction(), false);
    QCOMPARE(object.scriptClass(), (QScriptClass*)0);
    // prototype should be Object.prototype
    QCOMPARE(object.prototype().isValid(), true);
    QCOMPARE(object.prototype().isObject(), true);
    QCOMPARE(object.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true);
}

void tst_QScriptEngine::newArray()
{
    QScriptEngine eng;
    QScriptValue array = eng.newArray();
    QCOMPARE(array.isValid(), true);
    QCOMPARE(array.isArray(), true);
    QCOMPARE(array.isObject(), true);
    QVERIFY(!array.isFunction());
    QCOMPARE(array.scriptClass(), (QScriptClass*)0);
    // prototype should be Array.prototype
    QCOMPARE(array.prototype().isValid(), true);
    QCOMPARE(array.prototype().isArray(), true);
    QCOMPARE(array.prototype().strictlyEquals(eng.evaluate("Array.prototype")), true);
}

void tst_QScriptEngine::newArray_HooliganTask218092()
{
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("[].splice(0, 0, 'a')");
        QVERIFY(ret.isArray());
        QCOMPARE(ret.property("length").toInt32(), 0);
    }
    {
        QScriptValue ret = eng.evaluate("['a'].splice(0, 1, 'b')");
        QVERIFY(ret.isArray());
        QCOMPARE(ret.property("length").toInt32(), 1);
    }
    {
        QScriptValue ret = eng.evaluate("['a', 'b'].splice(0, 1, 'c')");
        QVERIFY(ret.isArray());
        QCOMPARE(ret.property("length").toInt32(), 1);
    }
    {
        QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(0, 2, 'd')");
        QVERIFY(ret.isArray());
        QCOMPARE(ret.property("length").toInt32(), 2);
    }
    {
        QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(1, 2, 'd', 'e', 'f')");
        QVERIFY(ret.isArray());
        QCOMPARE(ret.property("length").toInt32(), 2);
    }
}

void tst_QScriptEngine::newArray_HooliganTask233836()
{
    QScriptEngine eng;
    {
        // According to ECMA-262, this should cause a RangeError.
        QScriptValue ret = eng.evaluate("a = new Array(4294967295); a.push('foo')");
        QEXPECT_FAIL("", "ECMA compliance bug in Array.prototype.push: https://bugs.webkit.org/show_bug.cgi?id=55033", Continue);
        QVERIFY(ret.isError() && ret.toString().contains(QLatin1String("RangeError")));
    }
    {
        QScriptValue ret = eng.newArray(0xFFFFFFFF);
        QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
        ret.setProperty(0xFFFFFFFF, 123);
        QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
        QVERIFY(ret.property(0xFFFFFFFF).isNumber());
        QCOMPARE(ret.property(0xFFFFFFFF).toInt32(), 123);
        ret.setProperty(123, 456);
        QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
        QVERIFY(ret.property(123).isNumber());
        QCOMPARE(ret.property(123).toInt32(), 456);
    }
}

void tst_QScriptEngine::newVariant()
{
    QScriptEngine eng;
    {
        QScriptValue opaque = eng.newVariant(QVariant());
        QCOMPARE(opaque.isValid(), true);
        QCOMPARE(opaque.isVariant(), true);
        QVERIFY(!opaque.isFunction());
        QCOMPARE(opaque.isObject(), true);
        QCOMPARE(opaque.prototype().isValid(), true);
        QCOMPARE(opaque.prototype().isVariant(), true);
        QVERIFY(opaque.property("valueOf").call(opaque).isUndefined());
    }
}

void tst_QScriptEngine::newVariant_defaultPrototype()
{
    // default prototype should be set automatically
    QScriptEngine eng;
    {
        QScriptValue proto = eng.newObject();
        eng.setDefaultPrototype(qMetaTypeId<QString>(), proto);
        QScriptValue ret = eng.newVariant(QVariant(QString::fromLatin1("hello")));
        QVERIFY(ret.isVariant());
        QCOMPARE(ret.scriptClass(), (QScriptClass*)0);
        QVERIFY(ret.prototype().strictlyEquals(proto));
        eng.setDefaultPrototype(qMetaTypeId<QString>(), QScriptValue());
        QScriptValue ret2 = eng.newVariant(QVariant(QString::fromLatin1("hello")));
        QVERIFY(ret2.isVariant());
        QVERIFY(!ret2.prototype().strictlyEquals(proto));
    }
}

void tst_QScriptEngine::newVariant_promoteObject()
{
    // "promote" plain object to variant
    QScriptEngine eng;
    {
        QScriptValue object = eng.newObject();
        object.setProperty("foo", eng.newObject());
        object.setProperty("bar", object.property("foo"));
        QVERIFY(object.property("foo").isObject());
        QVERIFY(!object.property("foo").isVariant());
        QScriptValue originalProto = object.property("foo").prototype();
        QScriptValue ret = eng.newVariant(object.property("foo"), QVariant(123));
        QVERIFY(ret.isValid());
        QVERIFY(ret.strictlyEquals(object.property("foo")));
        QVERIFY(ret.isVariant());
        QVERIFY(object.property("foo").isVariant());
        QVERIFY(object.property("bar").isVariant());
        QCOMPARE(ret.toVariant(), QVariant(123));
        QVERIFY(ret.prototype().strictlyEquals(originalProto));
    }
}

void tst_QScriptEngine::newVariant_replaceValue()
{
    // replace value of existing object
    QScriptEngine eng;
    {
        QScriptValue object = eng.newVariant(QVariant(123));
        for (int x = 0; x < 2; ++x) {
            QScriptValue ret = eng.newVariant(object, QVariant(456));
            QVERIFY(ret.isValid());
            QVERIFY(ret.strictlyEquals(object));
            QVERIFY(ret.isVariant());
            QCOMPARE(ret.toVariant(), QVariant(456));
        }
    }
}

void tst_QScriptEngine::newVariant_valueOfToString()
{
    // valueOf() and toString()
    QScriptEngine eng;
    {
        QScriptValue object = eng.newVariant(QVariant(123));
        QScriptValue value = object.property("valueOf").call(object);
        QVERIFY(value.isNumber());
        QCOMPARE(value.toInt32(), 123);
        QCOMPARE(object.toString(), QString::fromLatin1("123"));
        QCOMPARE(object.toVariant().toString(), object.toString());
    }
    {
        QScriptValue object = eng.newVariant(QVariant(QString::fromLatin1("hello")));
        QScriptValue value = object.property("valueOf").call(object);
        QVERIFY(value.isString());
        QCOMPARE(value.toString(), QString::fromLatin1("hello"));
        QCOMPARE(object.toString(), QString::fromLatin1("hello"));
        QCOMPARE(object.toVariant().toString(), object.toString());
    }
    {
        QScriptValue object = eng.newVariant(QVariant(false));
        QScriptValue value = object.property("valueOf").call(object);
        QVERIFY(value.isBoolean());
        QCOMPARE(value.toBoolean(), false);
        QCOMPARE(object.toString(), QString::fromLatin1("false"));
        QCOMPARE(object.toVariant().toString(), object.toString());
    }
    {
        QScriptValue object = eng.newVariant(QVariant(QPoint(10, 20)));
        QScriptValue value = object.property("valueOf").call(object);
        QVERIFY(value.isObject());
        QVERIFY(value.strictlyEquals(object));
        QCOMPARE(object.toString(), QString::fromLatin1("QVariant(QPoint)"));
    }
}

void tst_QScriptEngine::newVariant_promoteNonObject()
{
    QScriptEngine eng;
    {
        QVariant var(456);
        QScriptValue ret = eng.newVariant(123, var);
        QVERIFY(ret.isVariant());
        QCOMPARE(ret.toVariant(), var);
    }
}

void tst_QScriptEngine::newVariant_promoteNonQScriptObject()
{
    QScriptEngine eng;
    {
        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::newVariant(): changing class of non-QScriptObject not supported");
        QScriptValue ret = eng.newVariant(eng.newArray(), 123);
        QVERIFY(!ret.isValid());
    }
}

void tst_QScriptEngine::newRegExp()
{
    QScriptEngine eng;
    for (int x = 0; x < 2; ++x) {
        QScriptValue rexp;
        if (x == 0)
            rexp = eng.newRegExp("foo", "bar");
        else
            rexp = eng.newRegExp(QRegExp("foo"));
        QCOMPARE(rexp.isValid(), true);
        QCOMPARE(rexp.isRegExp(), true);
        QCOMPARE(rexp.isObject(), true);
        QVERIFY(rexp.isFunction()); // in JSC, RegExp objects are callable
        // prototype should be RegExp.prototype
        QCOMPARE(rexp.prototype().isValid(), true);
        QCOMPARE(rexp.prototype().isObject(), true);
        QCOMPARE(rexp.prototype().isRegExp(), false);
        QCOMPARE(rexp.prototype().strictlyEquals(eng.evaluate("RegExp.prototype")), true);

        QCOMPARE(rexp.toRegExp().pattern(), QRegExp("foo").pattern());
    }
}

void tst_QScriptEngine::jsRegExp()
{
    // See ECMA-262 Section 15.10, "RegExp Objects".
    // These should really be JS-only tests, as they test the implementation's
    // ECMA-compliance, not the C++ API. Compliance should already be covered
    // by the Mozilla tests (qscriptjstestsuite).
    // We can consider updating the expected results of this test if the
    // RegExp implementation changes.

    QScriptEngine eng;
    QScriptValue r = eng.evaluate("/foo/gim");
    QVERIFY(r.isRegExp());
    QCOMPARE(r.toString(), QString::fromLatin1("/foo/gim"));

    QScriptValue rxCtor = eng.globalObject().property("RegExp");
    QScriptValue r2 = rxCtor.call(QScriptValue(), QScriptValueList() << r);
    QVERIFY(r2.isRegExp());
    QVERIFY(r2.strictlyEquals(r));

    QScriptValue r3 = rxCtor.call(QScriptValue(), QScriptValueList() << r << "gim");
    QVERIFY(r3.isError());
    QVERIFY(r3.toString().contains(QString::fromLatin1("TypeError"))); // Cannot supply flags when constructing one RegExp from another

    QScriptValue r4 = rxCtor.call(QScriptValue(), QScriptValueList() << "foo" << "gim");
    QVERIFY(r4.isRegExp());

    QScriptValue r5 = rxCtor.construct(QScriptValueList() << r);
    QVERIFY(r5.isRegExp());
    QCOMPARE(r5.toString(), QString::fromLatin1("/foo/gim"));
    // In JSC, constructing a RegExp from another produces the same identical object.
    // This is different from SpiderMonkey and old back-end.
    QEXPECT_FAIL("", "RegExp copy-constructor should return a new object: https://bugs.webkit.org/show_bug.cgi?id=55040", Continue);
    QVERIFY(!r5.strictlyEquals(r));

    QScriptValue r6 = rxCtor.construct(QScriptValueList() << "foo" << "bar");
    QVERIFY(r6.isError());
    QVERIFY(r6.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag

    QScriptValue r7 = eng.evaluate("/foo/gimp");
    QVERIFY(r7.isError());
    QVERIFY(r7.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag

    // JSC doesn't complain about duplicate flags.
    QScriptValue r8 = eng.evaluate("/foo/migmigmig");
    QVERIFY(r8.isRegExp());
    QCOMPARE(r8.toString(), QString::fromLatin1("/foo/gim"));

    QScriptValue r9 = rxCtor.construct();
    QVERIFY(r9.isRegExp());
    QCOMPARE(r9.toString(), QString::fromLatin1("/(?:)/"));

    QScriptValue r10 = rxCtor.construct(QScriptValueList() << "" << "gim");
    QVERIFY(r10.isRegExp());
    QCOMPARE(r10.toString(), QString::fromLatin1("/(?:)/gim"));

    QScriptValue r11 = rxCtor.construct(QScriptValueList() << "{1.*}" << "g");
    QVERIFY(r11.isRegExp());
    QCOMPARE(r11.toString(), QString::fromLatin1("/{1.*}/g"));
}

void tst_QScriptEngine::newDate()
{
    QScriptEngine eng;

    {
        QScriptValue date = eng.newDate(0);
        QCOMPARE(date.isValid(), true);
        QCOMPARE(date.isDate(), true);
        QCOMPARE(date.isObject(), true);
        QVERIFY(!date.isFunction());
        // prototype should be Date.prototype
        QCOMPARE(date.prototype().isValid(), true);
        QCOMPARE(date.prototype().isDate(), true);
        QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);
    }

    {
        QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::LocalTime);
        QScriptValue date = eng.newDate(dt);
        QCOMPARE(date.isValid(), true);
        QCOMPARE(date.isDate(), true);
        QCOMPARE(date.isObject(), true);
        // prototype should be Date.prototype
        QCOMPARE(date.prototype().isValid(), true);
        QCOMPARE(date.prototype().isDate(), true);
        QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);

        QCOMPARE(date.toDateTime(), dt);
    }

    {
        QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::UTC);
        QScriptValue date = eng.newDate(dt);
        // toDateTime() result should be in local time
        QCOMPARE(date.toDateTime(), dt.toLocalTime());
    }
}

void tst_QScriptEngine::jsParseDate()
{
    QScriptEngine eng;
    // Date.parse() should return NaN when it fails
    {
        QScriptValue ret = eng.evaluate("Date.parse()");
        QVERIFY(ret.isNumber());
        QVERIFY(qIsNaN(ret.toNumber()));
    }

    // Date.parse() should be able to parse the output of Date().toString()
#ifndef Q_WS_WIN // TODO: Test and remove this since 169701 has been fixed
    {
        QScriptValue ret = eng.evaluate("var x = new Date(); var s = x.toString(); s == new Date(Date.parse(s)).toString()");
        QVERIFY(ret.isBoolean());
        QCOMPARE(ret.toBoolean(), true);
    }
#endif
}

void tst_QScriptEngine::newQObject()
{
    QScriptEngine eng;

    {
        QScriptValue qobject = eng.newQObject(0);
        QCOMPARE(qobject.isValid(), true);
        QCOMPARE(qobject.isNull(), true);
        QCOMPARE(qobject.isObject(), false);
        QCOMPARE(qobject.toQObject(), (QObject *)0);
    }
    {
        QScriptValue qobject = eng.newQObject(this);
        QCOMPARE(qobject.isValid(), true);
        QCOMPARE(qobject.isQObject(), true);
        QCOMPARE(qobject.isObject(), true);
        QCOMPARE(qobject.toQObject(), (QObject *)this);
        QVERIFY(!qobject.isFunction());
        // prototype should be QObject.prototype
        QCOMPARE(qobject.prototype().isValid(), true);
        QCOMPARE(qobject.prototype().isQObject(), true);
        QCOMPARE(qobject.scriptClass(), (QScriptClass*)0);
    }
}

void tst_QScriptEngine::newQObject_ownership()
{
    QScriptEngine eng;
    {
        QPointer<QObject> ptr = new QObject();
        QVERIFY(ptr != 0);
        {
            QScriptValue v = eng.newQObject(ptr, QScriptEngine::ScriptOwnership);
        }
        eng.evaluate("gc()");
        if (ptr)
            QEXPECT_FAIL("", "In the JSC-based back-end, script-owned QObjects are not always deleted immediately during GC", Continue);
        QVERIFY(ptr == 0);
    }
    {
        QPointer<QObject> ptr = new QObject();
        QVERIFY(ptr != 0);
        {
            QScriptValue v = eng.newQObject(ptr, QScriptEngine::QtOwnership);
        }
        QObject *before = ptr;
        eng.evaluate("gc()");
        QVERIFY(ptr == before);
        delete ptr;
    }
    {
        QObject *parent = new QObject();
        QObject *child = new QObject(parent);
        QScriptValue v = eng.newQObject(child, QScriptEngine::QtOwnership);
        QCOMPARE(v.toQObject(), child);
        delete parent;
        QCOMPARE(v.toQObject(), (QObject *)0);
    }
    {
        QPointer<QObject> ptr = new QObject();
        QVERIFY(ptr != 0);
        {
            QScriptValue v = eng.newQObject(ptr, QScriptEngine::AutoOwnership);
        }
        eng.evaluate("gc()");
        // no parent, so it should be like ScriptOwnership
        if (ptr)
            QEXPECT_FAIL("", "In the JSC-based back-end, script-owned QObjects are not always deleted immediately during GC", Continue);
        QVERIFY(ptr == 0);
    }
    {
        QObject *parent = new QObject();
        QPointer<QObject> child = new QObject(parent);
        QVERIFY(child != 0);
        {
            QScriptValue v = eng.newQObject(child, QScriptEngine::AutoOwnership);
        }
        eng.evaluate("gc()");
        // has parent, so it should be like QtOwnership
        QVERIFY(child != 0);
        delete parent;
    }
}

void tst_QScriptEngine::newQObject_promoteObject()
{
    QScriptEngine eng;
    // "promote" plain object to QObject
    {
        QScriptValue obj = eng.newObject();
        QScriptValue originalProto = obj.prototype();
        QScriptValue ret = eng.newQObject(obj, this);
        QVERIFY(ret.isValid());
        QVERIFY(ret.isQObject());
        QVERIFY(ret.strictlyEquals(obj));
        QVERIFY(obj.isQObject());
        QCOMPARE(ret.toQObject(), (QObject *)this);
        QVERIFY(ret.prototype().strictlyEquals(originalProto));
        QScriptValue val = ret.property("objectName");
        QVERIFY(val.isString());
    }
    // "promote" variant object to QObject
    {
        QScriptValue obj = eng.newVariant(123);
        QVERIFY(obj.isVariant());
        QScriptValue originalProto = obj.prototype();
        QScriptValue ret = eng.newQObject(obj, this);
        QVERIFY(ret.isQObject());
        QVERIFY(ret.strictlyEquals(obj));
        QVERIFY(obj.isQObject());
        QCOMPARE(ret.toQObject(), (QObject *)this);
        QVERIFY(ret.prototype().strictlyEquals(originalProto));
    }
    // replace QObject* of existing object
    {
        QScriptValue object = eng.newVariant(123);
        QScriptValue originalProto = object.prototype();
        QObject otherQObject;
        for (int x = 0; x < 2; ++x) {
            QScriptValue ret = eng.newQObject(object, &otherQObject);
            QVERIFY(ret.isValid());
            QVERIFY(ret.isQObject());
            QVERIFY(ret.strictlyEquals(object));
            QCOMPARE(ret.toQObject(), (QObject *)&otherQObject);
            QVERIFY(ret.prototype().strictlyEquals(originalProto));
        }
    }
}

void tst_QScriptEngine::newQObject_sameQObject()
{
    QScriptEngine eng;
    // calling newQObject() several times with same object
    for (int x = 0; x < 2; ++x) {
        QObject qobj;
        // the default is to create a new wrapper object
        QScriptValue obj1 = eng.newQObject(&qobj);
        QScriptValue obj2 = eng.newQObject(&qobj);
        QVERIFY(!obj2.strictlyEquals(obj1));

        QScriptEngine::QObjectWrapOptions opt = 0;
        bool preferExisting = (x != 0);
        if (preferExisting)
            opt |= QScriptEngine::PreferExistingWrapperObject;

        QScriptValue obj3 = eng.newQObject(&qobj, QScriptEngine::AutoOwnership, opt);
        QVERIFY(!obj3.strictlyEquals(obj2));
        QScriptValue obj4 = eng.newQObject(&qobj, QScriptEngine::AutoOwnership, opt);
        QCOMPARE(obj4.strictlyEquals(obj3), preferExisting);

        QScriptValue obj5 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership, opt);
        QVERIFY(!obj5.strictlyEquals(obj4));
        QScriptValue obj6 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership, opt);
        QCOMPARE(obj6.strictlyEquals(obj5), preferExisting);

        QScriptValue obj7 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership,
                                           QScriptEngine::ExcludeSuperClassMethods | opt);
        QVERIFY(!obj7.strictlyEquals(obj6));
        QScriptValue obj8 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership,
                                           QScriptEngine::ExcludeSuperClassMethods | opt);
        QCOMPARE(obj8.strictlyEquals(obj7), preferExisting);
    }
}

void tst_QScriptEngine::newQObject_defaultPrototype()
{
    QScriptEngine eng;
    // newQObject() should set the default prototype, if one has been registered
    {
        QScriptValue oldQObjectProto = eng.defaultPrototype(qMetaTypeId<QObject*>());

        QScriptValue qobjectProto = eng.newObject();
        eng.setDefaultPrototype(qMetaTypeId<QObject*>(), qobjectProto);
        {
            QScriptValue ret = eng.newQObject(this);
            QVERIFY(ret.prototype().equals(qobjectProto));
        }
        QScriptValue tstProto = eng.newObject();
        int typeId = qRegisterMetaType<tst_QScriptEngine*>("tst_QScriptEngine*");
        eng.setDefaultPrototype(typeId, tstProto);
        {
            QScriptValue ret = eng.newQObject(this);
            QVERIFY(ret.prototype().equals(tstProto));
        }

        eng.setDefaultPrototype(qMetaTypeId<QObject*>(), oldQObjectProto);
        eng.setDefaultPrototype(typeId, QScriptValue());
    }
}

void tst_QScriptEngine::newQObject_promoteNonObject()
{
    QScriptEngine eng;
    {
        QScriptValue ret = eng.newQObject(123, this);
        QVERIFY(ret.isQObject());
        QCOMPARE(ret.toQObject(), this);
    }
}

void tst_QScriptEngine::newQObject_promoteNonQScriptObject()
{
    QScriptEngine eng;
    {
        QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::newQObject(): changing class of non-QScriptObject not supported");
        QScriptValue ret = eng.newQObject(eng.newArray(), this);
        QVERIFY(!ret.isValid());
    }
}

QT_BEGIN_NAMESPACE
Q_SCRIPT_DECLARE_QMETAOBJECT(QObject, QObject*)
Q_SCRIPT_DECLARE_QMETAOBJECT(QWidget, QWidget*)
QT_END_NAMESPACE

static QScriptValue myConstructor(QScriptContext *ctx, QScriptEngine *eng)
{
    QScriptValue obj;
    if (ctx->isCalledAsConstructor()) {
        obj = ctx->thisObject();
    } else {
        obj = eng->newObject();
        obj.setPrototype(ctx->callee().property("prototype"));
    }
    obj.setProperty("isCalledAsConstructor", QScriptValue(eng, ctx->isCalledAsConstructor()));
    return obj;
}

static QScriptValue instanceofJS(const QScriptValue &inst, const QScriptValue &ctor)
{
    return inst.engine()->evaluate("(function(inst, ctor) { return inst instanceof ctor; })")
        .call(QScriptValue(), QScriptValueList() << inst << ctor);
}

void tst_QScriptEngine::newQMetaObject()
{
    QScriptEngine eng;
#if 0
    QScriptValue qclass = eng.newQMetaObject<QObject>();
    QScriptValue qclass2 = eng.newQMetaObject<QWidget>();
#else
    QScriptValue qclass = qScriptValueFromQMetaObject<QObject>(&eng);
    QScriptValue qclass2 = qScriptValueFromQMetaObject<QWidget>(&eng);
#endif
    QCOMPARE(qclass.isValid(), true);
    QCOMPARE(qclass.isQMetaObject(), true);
    QCOMPARE(qclass.toQMetaObject(), &QObject::staticMetaObject);
    QCOMPARE(qclass.isFunction(), true);
    QVERIFY(qclass.property("prototype").isObject());

    QCOMPARE(qclass2.isValid(), true);
    QCOMPARE(qclass2.isQMetaObject(), true);
    QCOMPARE(qclass2.toQMetaObject(), &QWidget::staticMetaObject);
    QCOMPARE(qclass2.isFunction(), true);
    QVERIFY(qclass2.property("prototype").isObject());

    // prototype should be QMetaObject.prototype
    QCOMPARE(qclass.prototype().isObject(), true);
    QCOMPARE(qclass2.prototype().isObject(), true);

    QScriptValue instance = qclass.construct();
    QCOMPARE(instance.isQObject(), true);
    QCOMPARE(instance.toQObject()->metaObject(), qclass.toQMetaObject());
    QVERIFY(instance.instanceOf(qclass));
    QVERIFY(instanceofJS(instance, qclass).strictlyEquals(true));

    QScriptValue instance2 = qclass2.construct();
    QCOMPARE(instance2.isQObject(), true);
    QCOMPARE(instance2.toQObject()->metaObject(), qclass2.toQMetaObject());
    QVERIFY(instance2.instanceOf(qclass2));
    QVERIFY(instanceofJS(instance2, qclass2).strictlyEquals(true));
    QVERIFY(!instance2.instanceOf(qclass));
    QVERIFY(instanceofJS(instance2, qclass).strictlyEquals(false));

    QScriptValueList args;
    args << instance;
    QScriptValue instance3 = qclass.construct(args);
    QCOMPARE(instance3.isQObject(), true);
    QCOMPARE(instance3.toQObject()->parent(), instance.toQObject());
    QVERIFY(instance3.instanceOf(qclass));
    QVERIFY(instanceofJS(instance3, qclass).strictlyEquals(true));
    QVERIFY(!instance3.instanceOf(qclass2));
    QVERIFY(instanceofJS(instance3, qclass2).strictlyEquals(false));
    args.clear();

    QPointer<QObject> qpointer1 = instance.toQObject();
    QPointer<QObject> qpointer2 = instance2.toQObject();
    QPointer<QObject> qpointer3 = instance3.toQObject();

    QVERIFY(qpointer1);
    QVERIFY(qpointer2);
    QVERIFY(qpointer3);

    // verify that AutoOwnership is in effect
    instance = QScriptValue();
    collectGarbage_helper(eng);

    QVERIFY(!qpointer1);
    QVERIFY(qpointer2);
    QVERIFY(!qpointer3); // was child of instance

    QVERIFY(instance.toQObject() == 0);
    QVERIFY(instance3.toQObject() == 0); // was child of instance
    QVERIFY(instance2.toQObject() != 0);
    instance2 = QScriptValue();
    collectGarbage_helper(eng);
    QVERIFY(instance2.toQObject() == 0);

    // with custom constructor
    QScriptValue ctor = eng.newFunction(myConstructor);
    QScriptValue qclass3 = eng.newQMetaObject(&QObject::staticMetaObject, ctor);
    QVERIFY(qclass3.property("prototype").equals(ctor.property("prototype")));
    {
        QScriptValue ret = qclass3.call();
        QVERIFY(ret.isObject());
        QVERIFY(ret.property("isCalledAsConstructor").isBoolean());
        QVERIFY(!ret.property("isCalledAsConstructor").toBoolean());
        QVERIFY(ret.instanceOf(qclass3));
        QVERIFY(instanceofJS(ret, qclass3).strictlyEquals(true));
        QVERIFY(!ret.instanceOf(qclass));
        QVERIFY(instanceofJS(ret, qclass).strictlyEquals(false));
    }
    {
        QScriptValue ret = qclass3.construct();
        QVERIFY(ret.isObject());
        QVERIFY(ret.property("isCalledAsConstructor").isBoolean());
        QVERIFY(ret.property("isCalledAsConstructor").toBoolean());
        QVERIFY(ret.instanceOf(qclass3));
        QVERIFY(instanceofJS(ret, qclass3).strictlyEquals(true));
        QVERIFY(!ret.instanceOf(qclass2));
        QVERIFY(instanceofJS(ret, qclass2).strictlyEquals(false));
    }

    // subclassing
    qclass2.setProperty("prototype", qclass.construct());
    QVERIFY(qclass2.construct().instanceOf(qclass));
    QVERIFY(instanceofJS(qclass2.construct(), qclass).strictlyEquals(true));

    // with meta-constructor
    QScriptValue qclass4 = eng.newQMetaObject(&QObject::staticMetaObject);
    {
        QScriptValue inst = qclass4.construct();
        QVERIFY(inst.isQObject());
        QVERIFY(inst.toQObject() != 0);
        QCOMPARE(inst.toQObject()->parent(), (QObject*)0);
        QVERIFY(inst.instanceOf(qclass4));
        QVERIFY(instanceofJS(inst, qclass4).strictlyEquals(true));
        QVERIFY(!inst.instanceOf(qclass3));
        QVERIFY(instanceofJS(inst, qclass3).strictlyEquals(false));
    }
    {
        QScriptValue inst = qclass4.construct(QScriptValueList() << eng.newQObject(this));
        QVERIFY(inst.isQObject());
        QVERIFY(inst.toQObject() != 0);
        QCOMPARE(inst.toQObject()->parent(), (QObject*)this);
        QVERIFY(inst.instanceOf(qclass4));
        QVERIFY(instanceofJS(inst, qclass4).strictlyEquals(true));
        QVERIFY(!inst.instanceOf(qclass2));
        QVERIFY(instanceofJS(inst, qclass2).strictlyEquals(false));
    }
}

void tst_QScriptEngine::newActivationObject()
{
    QSKIP("internal function not implemented in JSC-based back-end");
    QScriptEngine eng;
    QScriptValue act = eng.newActivationObject();
    QEXPECT_FAIL("", "", Continue);
    QCOMPARE(act.isValid(), true);
    QEXPECT_FAIL("", "", Continue);
    QCOMPARE(act.isObject(), true);
    QVERIFY(!act.isFunction());
    QScriptValue v(&eng, 123);
    act.setProperty("prop", v);
    QEXPECT_FAIL("", "", Continue);
    QCOMPARE(act.property("prop").strictlyEquals(v), true);
    QCOMPARE(act.scope().isValid(), false);
    QEXPECT_FAIL("", "", Continue);
    QVERIFY(act.prototype().isNull());
}

void tst_QScriptEngine::getSetGlobalObject()
{
    QScriptEngine eng;
    QScriptValue glob = eng.globalObject();
    QCOMPARE(glob.isValid(), true);
    QCOMPARE(glob.isObject(), true);
    QVERIFY(!glob.isFunction());
    QVERIFY(eng.currentContext()->thisObject().strictlyEquals(glob));
    QVERIFY(eng.currentContext()->activationObject().strictlyEquals(glob));
    QCOMPARE(glob.toString(), QString::fromLatin1("[object global]"));
    // prototype should be Object.prototype
    QCOMPARE(glob.prototype().isValid(), true);
    QCOMPARE(glob.prototype().isObject(), true);
    QCOMPARE(glob.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true);

    eng.setGlobalObject(glob);
    QVERIFY(eng.globalObject().equals(glob));
    eng.setGlobalObject(123);
    QVERIFY(eng.globalObject().equals(glob));

    QScriptValue obj = eng.newObject();
    eng.setGlobalObject(obj);
    QVERIFY(eng.globalObject().strictlyEquals(obj));
    QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
    QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));
    QVERIFY(eng.evaluate("this").strictlyEquals(obj));
    QCOMPARE(eng.globalObject().toString(), QString::fromLatin1("[object Object]"));

    glob = QScriptValue(); // kill reference to old global object
    collectGarbage_helper(eng);
    obj = eng.newObject();
    eng.setGlobalObject(obj);
    QVERIFY(eng.globalObject().strictlyEquals(obj));
    QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
    QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));

    collectGarbage_helper(eng);
    QVERIFY(eng.globalObject().strictlyEquals(obj));
    QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
    QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));

    QVERIFY(!obj.property("foo").isValid());
    eng.evaluate("var foo = 123");
    {
        QScriptValue ret = obj.property("foo");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
    }

    QVERIFY(!obj.property("bar").isValid());
    eng.evaluate("bar = 456");
    {
        QScriptValue ret = obj.property("bar");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 456);
    }

    QVERIFY(!obj.property("baz").isValid());
    eng.evaluate("this['baz'] = 789");
    {
        QScriptValue ret = obj.property("baz");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 789);
    }

    {
        QScriptValue ret = eng.evaluate("(function() { return this; })()");
        QVERIFY(ret.strictlyEquals(obj));
    }

    // Delete property.
    {
        QScriptValue ret = eng.evaluate("delete foo");
        QVERIFY(ret.isBool());
        QVERIFY(ret.toBool());
        QVERIFY(!obj.property("foo").isValid());
    }

    // Getter/setter property.
    QVERIFY(eng.evaluate("this.__defineGetter__('oof', function() { return this.bar; })").isUndefined());
    QVERIFY(eng.evaluate("this.__defineSetter__('oof', function(v) { this.bar = v; })").isUndefined());
    QVERIFY(eng.evaluate("this.__lookupGetter__('oof')").isFunction());
    QVERIFY(eng.evaluate("this.__lookupSetter__('oof')").isFunction());
    eng.evaluate("oof = 123");
    QVERIFY(eng.evaluate("oof").equals(obj.property("bar")));

    // Enumeration.
    {
        QScriptValue ret = eng.evaluate("a = []; for (var p in this) a.push(p); a");
        QCOMPARE(ret.toString(), QString::fromLatin1("bar,baz,oof,p,a"));
    }
}

static QScriptValue getSetFoo(QScriptContext *ctx, QScriptEngine *)
{
    if (ctx->argumentCount() > 0)
        ctx->thisObject().setProperty("foo", ctx->argument(0));
    return ctx->thisObject().property("foo");
}

void tst_QScriptEngine::globalObjectProperties()
{
    // See ECMA-262 Section 15.1, "The Global Object".

    QScriptEngine eng;
    QScriptValue global = eng.globalObject();

    QVERIFY(global.property("NaN").isNumber());
    QVERIFY(qIsNaN(global.property("NaN").toNumber()));
    QCOMPARE(global.propertyFlags("NaN"), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable);

    QVERIFY(global.property("Infinity").isNumber());
    QVERIFY(qIsInf(global.property("Infinity").toNumber()));
    QCOMPARE(global.propertyFlags("NaN"), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable);

    QVERIFY(global.property("undefined").isUndefined());
    QCOMPARE(global.propertyFlags("undefined"), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable);

    QVERIFY(global.property("eval").isFunction());
    QCOMPARE(global.propertyFlags("eval"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("parseInt").isFunction());
    QCOMPARE(global.propertyFlags("parseInt"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("parseFloat").isFunction());
    QCOMPARE(global.propertyFlags("parseFloat"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("isNaN").isFunction());
    QCOMPARE(global.propertyFlags("isNaN"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("isFinite").isFunction());
    QCOMPARE(global.propertyFlags("isFinite"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("decodeURI").isFunction());
    QCOMPARE(global.propertyFlags("decodeURI"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("decodeURIComponent").isFunction());
    QCOMPARE(global.propertyFlags("decodeURIComponent"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("encodeURI").isFunction());
    QCOMPARE(global.propertyFlags("encodeURI"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("encodeURIComponent").isFunction());
    QCOMPARE(global.propertyFlags("encodeURIComponent"), QScriptValue::SkipInEnumeration);

    QVERIFY(global.property("Object").isFunction());
    QCOMPARE(global.propertyFlags("Object"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("Function").isFunction());
    QCOMPARE(global.propertyFlags("Function"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("Array").isFunction());
    QCOMPARE(global.propertyFlags("Array"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("String").isFunction());
    QCOMPARE(global.propertyFlags("String"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("Boolean").isFunction());
    QCOMPARE(global.propertyFlags("Boolean"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("Number").isFunction());
    QCOMPARE(global.propertyFlags("Number"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("Date").isFunction());
    QCOMPARE(global.propertyFlags("Date"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("RegExp").isFunction());
    QCOMPARE(global.propertyFlags("RegExp"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("Error").isFunction());
    QCOMPARE(global.propertyFlags("Error"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("EvalError").isFunction());
    QCOMPARE(global.propertyFlags("EvalError"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("RangeError").isFunction());
    QCOMPARE(global.propertyFlags("RangeError"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("ReferenceError").isFunction());
    QCOMPARE(global.propertyFlags("ReferenceError"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("SyntaxError").isFunction());
    QCOMPARE(global.propertyFlags("SyntaxError"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("TypeError").isFunction());
    QCOMPARE(global.propertyFlags("TypeError"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("URIError").isFunction());
    QCOMPARE(global.propertyFlags("URIError"), QScriptValue::SkipInEnumeration);
    QVERIFY(global.property("Math").isObject());
    QVERIFY(!global.property("Math").isFunction());
    QEXPECT_FAIL("", "[ECMA compliance] JSC sets DontDelete flag for Math object: https://bugs.webkit.org/show_bug.cgi?id=55034", Continue);
    QCOMPARE(global.propertyFlags("Math"), QScriptValue::SkipInEnumeration);
}

void tst_QScriptEngine::globalObjectProperties_enumerate()
{
    QScriptEngine eng;
    QScriptValue global = eng.globalObject();

    QSet<QString> expectedNames;
    expectedNames
        << "isNaN"
        << "parseFloat"
        << "String"
        << "EvalError"
        << "URIError"
        << "Math"
        << "encodeURIComponent"
        << "RangeError"
        << "eval"
        << "isFinite"
        << "ReferenceError"
        << "Infinity"
        << "Function"
        << "RegExp"
        << "Number"
        << "parseInt"
        << "Object"
        << "decodeURI"
        << "TypeError"
        << "Boolean"
        << "encodeURI"
        << "NaN"
        << "Error"
        << "decodeURIComponent"
        << "Date"
        << "Array"
        << "escape"
        << "unescape"
        << "SyntaxError"
        << "undefined"
        // non-standard
        << "gc"
        << "version"
        << "print"
        // JavaScriptCore
        << "JSON"
        ;
    QSet<QString> actualNames;
    {
        QScriptValueIterator it(global);
        while (it.hasNext()) {
            it.next();
            actualNames.insert(it.name());
        }
    }

    QSet<QString> remainingNames = actualNames;
    {
        QSet<QString>::const_iterator it;
        for (it = expectedNames.constBegin(); it != expectedNames.constEnd(); ++it) {
            QString name = *it;
            QVERIFY(actualNames.contains(name));
            remainingNames.remove(name);
        }
    }
    QVERIFY(remainingNames.isEmpty());
}

void tst_QScriptEngine::createGlobalObjectProperty()
{
    QScriptEngine eng;
    QScriptValue global = eng.globalObject();
    // create property with no attributes
    {
        QString name = QString::fromLatin1("foo");
        QVERIFY(!global.property(name).isValid());
        QScriptValue val(123);
        global.setProperty(name, val);
        QVERIFY(global.property(name).equals(val));
        QVERIFY(global.propertyFlags(name) == 0);
        global.setProperty(name, QScriptValue());
        QVERIFY(!global.property(name).isValid());
    }
    // create property with attributes
    {
        QString name = QString::fromLatin1("bar");
        QVERIFY(!global.property(name).isValid());
        QScriptValue val(QString::fromLatin1("ciao"));
        QScriptValue::PropertyFlags flags = QScriptValue::ReadOnly | QScriptValue::SkipInEnumeration;
        global.setProperty(name, val, flags);
        QVERIFY(global.property(name).equals(val));
        QEXPECT_FAIL("", "QTBUG-6134: custom Global Object properties don't retain attributes", Continue);
        QCOMPARE(global.propertyFlags(name), flags);
        global.setProperty(name, QScriptValue());
        QVERIFY(!global.property(name).isValid());
    }
}

void tst_QScriptEngine::globalObjectGetterSetterProperty()
{
    QScriptEngine engine;
    QScriptValue global = engine.globalObject();
    global.setProperty("bar", engine.newFunction(getSetFoo),
                       QScriptValue::PropertySetter | QScriptValue::PropertyGetter);
    global.setProperty("foo", 123);
    QVERIFY(global.property("bar").equals(global.property("foo")));
    QVERIFY(engine.evaluate("bar").equals(global.property("foo")));
    global.setProperty("bar", 456);
    QVERIFY(global.property("bar").equals(global.property("foo")));

    engine.evaluate("__defineGetter__('baz', function() { return 789; })");
    QVERIFY(engine.evaluate("baz").equals(789));
    QVERIFY(global.property("baz").equals(789));
}

void tst_QScriptEngine::customGlobalObjectWithPrototype()
{
    for (int x = 0; x < 2; ++x) {
        QScriptEngine engine;
        QScriptValue wrap = engine.newObject();
        QScriptValue global = engine.globalObject();
        QScriptValue originalGlobalProto = global.prototype();
        if (!x) {
            // Set prototype before setting global object
            wrap.setPrototype(global);
            QVERIFY(wrap.prototype().strictlyEquals(global));
            engine.setGlobalObject(wrap);
        } else {
            // Set prototype after setting global object
            engine.setGlobalObject(wrap);
            wrap.setPrototype(global);
            QVERIFY(wrap.prototype().strictlyEquals(global));
        }
        {
            QScriptValue ret = engine.evaluate("print");
            QVERIFY(ret.isFunction());
            QVERIFY(ret.strictlyEquals(wrap.property("print")));
        }
        {
            QScriptValue ret = engine.evaluate("this.print");
            QVERIFY(ret.isFunction());
            QVERIFY(ret.strictlyEquals(wrap.property("print")));
        }
        {
            QScriptValue ret = engine.evaluate("hasOwnProperty('print')");
            QVERIFY(ret.isBool());
            QVERIFY(!ret.toBool());
        }
        {
            QScriptValue ret = engine.evaluate("this.hasOwnProperty('print')");
            QVERIFY(ret.isBool());
            QVERIFY(!ret.toBool());
        }

        QScriptValue anotherProto = engine.newObject();
        anotherProto.setProperty("anotherProtoProperty", 123);
        global.setPrototype(anotherProto);
        {
            QScriptValue ret = engine.evaluate("print");
            QVERIFY(ret.isFunction());
            QVERIFY(ret.strictlyEquals(wrap.property("print")));
        }
        {
            QScriptValue ret = engine.evaluate("anotherProtoProperty");
            QVERIFY(ret.isNumber());
            QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
        }
        {
            QScriptValue ret = engine.evaluate("this.anotherProtoProperty");
            QVERIFY(ret.isNumber());
            QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
        }

        wrap.setPrototype(anotherProto);
        {
            QScriptValue ret = engine.evaluate("print");
            QVERIFY(ret.isError());
            QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: print"));
        }
        {
            QScriptValue ret = engine.evaluate("anotherProtoProperty");
            QVERIFY(ret.isNumber());
            QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
        }
        QVERIFY(global.prototype().strictlyEquals(anotherProto));

        global.setPrototype(originalGlobalProto);
        engine.setGlobalObject(global);
        {
            QScriptValue ret = engine.evaluate("anotherProtoProperty");
            QVERIFY(ret.isError());
            QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: anotherProtoProperty"));
        }
        {
            QScriptValue ret = engine.evaluate("print");
            QVERIFY(ret.isFunction());
            QVERIFY(ret.strictlyEquals(global.property("print")));
        }
        QVERIFY(!anotherProto.property("print").isValid());
    }
}

void tst_QScriptEngine::globalObjectWithCustomPrototype()
{
    QScriptEngine engine;
    QScriptValue proto = engine.newObject();
    proto.setProperty("protoProperty", 123);
    QScriptValue global = engine.globalObject();
    QScriptValue originalProto = global.prototype();
    global.setPrototype(proto);
    {
        QScriptValue ret = engine.evaluate("protoProperty");
        QVERIFY(ret.isNumber());
        QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
    }
    {
        QScriptValue ret = engine.evaluate("this.protoProperty");
        QVERIFY(ret.isNumber());
        QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
    }
    {
        QScriptValue ret = engine.evaluate("hasOwnProperty('protoProperty')");
        QVERIFY(ret.isBool());
        QVERIFY(!ret.toBool());
    }
    {
        QScriptValue ret = engine.evaluate("this.hasOwnProperty('protoProperty')");
        QVERIFY(ret.isBool());
        QVERIFY(!ret.toBool());
    }

    // Custom prototype set from JS
    {
        QScriptValue ret = engine.evaluate("this.__proto__ = { 'a': 123 }; a");
        QVERIFY(ret.isNumber());
        QEXPECT_FAIL("", "QTBUG-9737: Prototype change in JS not reflected on C++ side", Continue);
        QVERIFY(ret.strictlyEquals(global.property("a")));
    }
}

void tst_QScriptEngine::builtinFunctionNames_data()
{
    QTest::addColumn<QString>("expression");
    QTest::addColumn<QString>("expectedName");

    // See ECMA-262 Chapter 15, "Standard Built-in ECMAScript Objects".

    QTest::newRow("print") << QString("print") << QString("print"); // Qt Script extension.
    QTest::newRow("parseInt") << QString("parseInt") << QString("parseInt");
    QTest::newRow("parseFloat") << QString("parseFloat") << QString("parseFloat");
    QTest::newRow("isNaN") << QString("isNaN") << QString("isNaN");
    QTest::newRow("isFinite") << QString("isFinite") << QString("isFinite");
    QTest::newRow("decodeURI") << QString("decodeURI") << QString("decodeURI");
    QTest::newRow("decodeURIComponent") << QString("decodeURIComponent") << QString("decodeURIComponent");
    QTest::newRow("encodeURI") << QString("encodeURI") << QString("encodeURI");
    QTest::newRow("encodeURIComponent") << QString("encodeURIComponent") << QString("encodeURIComponent");
    QTest::newRow("escape") << QString("escape") << QString("escape");
    QTest::newRow("unescape") << QString("unescape") << QString("unescape");
    QTest::newRow("version") << QString("version") << QString("version"); // Qt Script extension.
    QTest::newRow("gc") << QString("gc") << QString("gc"); // Qt Script extension.

    QTest::newRow("Array") << QString("Array") << QString("Array");
    QTest::newRow("Array.prototype.toString") << QString("Array.prototype.toString") << QString("toString");
    QTest::newRow("Array.prototype.toLocaleString") << QString("Array.prototype.toLocaleString") << QString("toLocaleString");
    QTest::newRow("Array.prototype.concat") << QString("Array.prototype.concat") << QString("concat");
    QTest::newRow("Array.prototype.join") << QString("Array.prototype.join") << QString("join");
    QTest::newRow("Array.prototype.pop") << QString("Array.prototype.pop") << QString("pop");
    QTest::newRow("Array.prototype.push") << QString("Array.prototype.push") << QString("push");
    QTest::newRow("Array.prototype.reverse") << QString("Array.prototype.reverse") << QString("reverse");
    QTest::newRow("Array.prototype.shift") << QString("Array.prototype.shift") << QString("shift");
    QTest::newRow("Array.prototype.slice") << QString("Array.prototype.slice") << QString("slice");
    QTest::newRow("Array.prototype.sort") << QString("Array.prototype.sort") << QString("sort");
    QTest::newRow("Array.prototype.splice") << QString("Array.prototype.splice") << QString("splice");
    QTest::newRow("Array.prototype.unshift") << QString("Array.prototype.unshift") << QString("unshift");

    QTest::newRow("Boolean") << QString("Boolean") << QString("Boolean");
    QTest::newRow("Boolean.prototype.toString") << QString("Boolean.prototype.toString") << QString("toString");

    QTest::newRow("Date") << QString("Date") << QString("Date");
    QTest::newRow("Date.prototype.toString") << QString("Date.prototype.toString") << QString("toString");
    QTest::newRow("Date.prototype.toDateString") << QString("Date.prototype.toDateString") << QString("toDateString");
    QTest::newRow("Date.prototype.toTimeString") << QString("Date.prototype.toTimeString") << QString("toTimeString");
    QTest::newRow("Date.prototype.toLocaleString") << QString("Date.prototype.toLocaleString") << QString("toLocaleString");
    QTest::newRow("Date.prototype.toLocaleDateString") << QString("Date.prototype.toLocaleDateString") << QString("toLocaleDateString");
    QTest::newRow("Date.prototype.toLocaleTimeString") << QString("Date.prototype.toLocaleTimeString") << QString("toLocaleTimeString");
    QTest::newRow("Date.prototype.valueOf") << QString("Date.prototype.valueOf") << QString("valueOf");
    QTest::newRow("Date.prototype.getTime") << QString("Date.prototype.getTime") << QString("getTime");
    QTest::newRow("Date.prototype.getYear") << QString("Date.prototype.getYear") << QString("getYear");
    QTest::newRow("Date.prototype.getFullYear") << QString("Date.prototype.getFullYear") << QString("getFullYear");
    QTest::newRow("Date.prototype.getUTCFullYear") << QString("Date.prototype.getUTCFullYear") << QString("getUTCFullYear");
    QTest::newRow("Date.prototype.getMonth") << QString("Date.prototype.getMonth") << QString("getMonth");
    QTest::newRow("Date.prototype.getUTCMonth") << QString("Date.prototype.getUTCMonth") << QString("getUTCMonth");
    QTest::newRow("Date.prototype.getDate") << QString("Date.prototype.getDate") << QString("getDate");
    QTest::newRow("Date.prototype.getUTCDate") << QString("Date.prototype.getUTCDate") << QString("getUTCDate");
    QTest::newRow("Date.prototype.getDay") << QString("Date.prototype.getDay") << QString("getDay");
    QTest::newRow("Date.prototype.getUTCDay") << QString("Date.prototype.getUTCDay") << QString("getUTCDay");
    QTest::newRow("Date.prototype.getHours") << QString("Date.prototype.getHours") << QString("getHours");
    QTest::newRow("Date.prototype.getUTCHours") << QString("Date.prototype.getUTCHours") << QString("getUTCHours");
    QTest::newRow("Date.prototype.getMinutes") << QString("Date.prototype.getMinutes") << QString("getMinutes");
    QTest::newRow("Date.prototype.getUTCMinutes") << QString("Date.prototype.getUTCMinutes") << QString("getUTCMinutes");
    QTest::newRow("Date.prototype.getSeconds") << QString("Date.prototype.getSeconds") << QString("getSeconds");
    QTest::newRow("Date.prototype.getUTCSeconds") << QString("Date.prototype.getUTCSeconds") << QString("getUTCSeconds");
    QTest::newRow("Date.prototype.getMilliseconds") << QString("Date.prototype.getMilliseconds") << QString("getMilliseconds");
    QTest::newRow("Date.prototype.getUTCMilliseconds") << QString("Date.prototype.getUTCMilliseconds") << QString("getUTCMilliseconds");
    QTest::newRow("Date.prototype.getTimezoneOffset") << QString("Date.prototype.getTimezoneOffset") << QString("getTimezoneOffset");
    QTest::newRow("Date.prototype.setTime") << QString("Date.prototype.setTime") << QString("setTime");
    QTest::newRow("Date.prototype.setMilliseconds") << QString("Date.prototype.setMilliseconds") << QString("setMilliseconds");
    QTest::newRow("Date.prototype.setUTCMilliseconds") << QString("Date.prototype.setUTCMilliseconds") << QString("setUTCMilliseconds");
    QTest::newRow("Date.prototype.setSeconds") << QString("Date.prototype.setSeconds") << QString("setSeconds");
    QTest::newRow("Date.prototype.setUTCSeconds") << QString("Date.prototype.setUTCSeconds") << QString("setUTCSeconds");
    QTest::newRow("Date.prototype.setMinutes") << QString("Date.prototype.setMinutes") << QString("setMinutes");
    QTest::newRow("Date.prototype.setUTCMinutes") << QString("Date.prototype.setUTCMinutes") << QString("setUTCMinutes");
    QTest::newRow("Date.prototype.setHours") << QString("Date.prototype.setHours") << QString("setHours");
    QTest::newRow("Date.prototype.setUTCHours") << QString("Date.prototype.setUTCHours") << QString("setUTCHours");
    QTest::newRow("Date.prototype.setDate") << QString("Date.prototype.setDate") << QString("setDate");
    QTest::newRow("Date.prototype.setUTCDate") << QString("Date.prototype.setUTCDate") << QString("setUTCDate");
    QTest::newRow("Date.prototype.setMonth") << QString("Date.prototype.setMonth") << QString("setMonth");
    QTest::newRow("Date.prototype.setUTCMonth") << QString("Date.prototype.setUTCMonth") << QString("setUTCMonth");
    QTest::newRow("Date.prototype.setYear") << QString("Date.prototype.setYear") << QString("setYear");
    QTest::newRow("Date.prototype.setFullYear") << QString("Date.prototype.setFullYear") << QString("setFullYear");
    QTest::newRow("Date.prototype.setUTCFullYear") << QString("Date.prototype.setUTCFullYear") << QString("setUTCFullYear");
    QTest::newRow("Date.prototype.toUTCString") << QString("Date.prototype.toUTCString") << QString("toUTCString");
    QTest::newRow("Date.prototype.toGMTString") << QString("Date.prototype.toGMTString") << QString("toGMTString");

    QTest::newRow("Error") << QString("Error") << QString("Error");
//    QTest::newRow("Error.prototype.backtrace") << QString("Error.prototype.backtrace") << QString("backtrace");
    QTest::newRow("Error.prototype.toString") << QString("Error.prototype.toString") << QString("toString");

    QTest::newRow("EvalError") << QString("EvalError") << QString("EvalError");
    QTest::newRow("RangeError") << QString("RangeError") << QString("RangeError");
    QTest::newRow("ReferenceError") << QString("ReferenceError") << QString("ReferenceError");
    QTest::newRow("SyntaxError") << QString("SyntaxError") << QString("SyntaxError");
    QTest::newRow("TypeError") << QString("TypeError") << QString("TypeError");
    QTest::newRow("URIError") << QString("URIError") << QString("URIError");

    QTest::newRow("Function") << QString("Function") << QString("Function");
    QTest::newRow("Function.prototype.toString") << QString("Function.prototype.toString") << QString("toString");
    QTest::newRow("Function.prototype.apply") << QString("Function.prototype.apply") << QString("apply");
    QTest::newRow("Function.prototype.call") << QString("Function.prototype.call") << QString("call");
    QTest::newRow("Function.prototype.connect") << QString("Function.prototype.connect") << QString("connect");
    QTest::newRow("Function.prototype.disconnect") << QString("Function.prototype.disconnect") << QString("disconnect");

    QTest::newRow("Math.abs") << QString("Math.abs") << QString("abs");
    QTest::newRow("Math.acos") << QString("Math.acos") << QString("acos");
    QTest::newRow("Math.asin") << QString("Math.asin") << QString("asin");
    QTest::newRow("Math.atan") << QString("Math.atan") << QString("atan");
    QTest::newRow("Math.atan2") << QString("Math.atan2") << QString("atan2");
    QTest::newRow("Math.ceil") << QString("Math.ceil") << QString("ceil");
    QTest::newRow("Math.cos") << QString("Math.cos") << QString("cos");
    QTest::newRow("Math.exp") << QString("Math.exp") << QString("exp");
    QTest::newRow("Math.floor") << QString("Math.floor") << QString("floor");
    QTest::newRow("Math.log") << QString("Math.log") << QString("log");
    QTest::newRow("Math.max") << QString("Math.max") << QString("max");
    QTest::newRow("Math.min") << QString("Math.min") << QString("min");
    QTest::newRow("Math.pow") << QString("Math.pow") << QString("pow");
    QTest::newRow("Math.random") << QString("Math.random") << QString("random");
    QTest::newRow("Math.round") << QString("Math.round") << QString("round");
    QTest::newRow("Math.sin") << QString("Math.sin") << QString("sin");
    QTest::newRow("Math.sqrt") << QString("Math.sqrt") << QString("sqrt");
    QTest::newRow("Math.tan") << QString("Math.tan") << QString("tan");

    QTest::newRow("Number") << QString("Number") << QString("Number");
    QTest::newRow("Number.prototype.toString") << QString("Number.prototype.toString") << QString("toString");
    QTest::newRow("Number.prototype.toLocaleString") << QString("Number.prototype.toLocaleString") << QString("toLocaleString");
    QTest::newRow("Number.prototype.valueOf") << QString("Number.prototype.valueOf") << QString("valueOf");
    QTest::newRow("Number.prototype.toFixed") << QString("Number.prototype.toFixed") << QString("toFixed");
    QTest::newRow("Number.prototype.toExponential") << QString("Number.prototype.toExponential") << QString("toExponential");
    QTest::newRow("Number.prototype.toPrecision") << QString("Number.prototype.toPrecision") << QString("toPrecision");

    QTest::newRow("Object") << QString("Object") << QString("Object");
    QTest::newRow("Object.prototype.toString") << QString("Object.prototype.toString") << QString("toString");
    QTest::newRow("Object.prototype.toLocaleString") << QString("Object.prototype.toLocaleString") << QString("toLocaleString");
    QTest::newRow("Object.prototype.valueOf") << QString("Object.prototype.valueOf") << QString("valueOf");
    QTest::newRow("Object.prototype.hasOwnProperty") << QString("Object.prototype.hasOwnProperty") << QString("hasOwnProperty");
    QTest::newRow("Object.prototype.isPrototypeOf") << QString("Object.prototype.isPrototypeOf") << QString("isPrototypeOf");
    QTest::newRow("Object.prototype.propertyIsEnumerable") << QString("Object.prototype.propertyIsEnumerable") << QString("propertyIsEnumerable");
    QTest::newRow("Object.prototype.__defineGetter__") << QString("Object.prototype.__defineGetter__") << QString("__defineGetter__");
    QTest::newRow("Object.prototype.__defineSetter__") << QString("Object.prototype.__defineSetter__") << QString("__defineSetter__");

    QTest::newRow("RegExp") << QString("RegExp") << QString("RegExp");
    QTest::newRow("RegExp.prototype.exec") << QString("RegExp.prototype.exec") << QString("exec");
    QTest::newRow("RegExp.prototype.test") << QString("RegExp.prototype.test") << QString("test");
    QTest::newRow("RegExp.prototype.toString") << QString("RegExp.prototype.toString") << QString("toString");

    QTest::newRow("String") << QString("String") << QString("String");
    QTest::newRow("String.prototype.toString") << QString("String.prototype.toString") << QString("toString");
    QTest::newRow("String.prototype.valueOf") << QString("String.prototype.valueOf") << QString("valueOf");
    QTest::newRow("String.prototype.charAt") << QString("String.prototype.charAt") << QString("charAt");
    QTest::newRow("String.prototype.charCodeAt") << QString("String.prototype.charCodeAt") << QString("charCodeAt");
    QTest::newRow("String.prototype.concat") << QString("String.prototype.concat") << QString("concat");
    QTest::newRow("String.prototype.indexOf") << QString("String.prototype.indexOf") << QString("indexOf");
    QTest::newRow("String.prototype.lastIndexOf") << QString("String.prototype.lastIndexOf") << QString("lastIndexOf");
    QTest::newRow("String.prototype.localeCompare") << QString("String.prototype.localeCompare") << QString("localeCompare");
    QTest::newRow("String.prototype.match") << QString("String.prototype.match") << QString("match");
    QTest::newRow("String.prototype.replace") << QString("String.prototype.replace") << QString("replace");
    QTest::newRow("String.prototype.search") << QString("String.prototype.search") << QString("search");
    QTest::newRow("String.prototype.slice") << QString("String.prototype.slice") << QString("slice");
    QTest::newRow("String.prototype.split") << QString("String.prototype.split") << QString("split");
    QTest::newRow("String.prototype.substring") << QString("String.prototype.substring") << QString("substring");
    QTest::newRow("String.prototype.toLowerCase") << QString("String.prototype.toLowerCase") << QString("toLowerCase");
    QTest::newRow("String.prototype.toLocaleLowerCase") << QString("String.prototype.toLocaleLowerCase") << QString("toLocaleLowerCase");
    QTest::newRow("String.prototype.toUpperCase") << QString("String.prototype.toUpperCase") << QString("toUpperCase");
    QTest::newRow("String.prototype.toLocaleUpperCase") << QString("String.prototype.toLocaleUpperCase") << QString("toLocaleUpperCase");
}

void tst_QScriptEngine::builtinFunctionNames()
{
    QFETCH(QString, expression);
    QFETCH(QString, expectedName);
    QScriptEngine eng;
    // The "name" property is actually non-standard, but JSC supports it.
    QScriptValue ret = eng.evaluate(QString::fromLatin1("%0.name").arg(expression));
    QVERIFY(ret.isString());
    QCOMPARE(ret.toString(), expectedName);
}

void tst_QScriptEngine::checkSyntax_data()
{
    QTest::addColumn<QString>("code");
    QTest::addColumn<int>("expectedState");
    QTest::addColumn<int>("errorLineNumber");
    QTest::addColumn<int>("errorColumnNumber");
    QTest::addColumn<QString>("errorMessage");

    QTest::newRow("0")
        << QString("0") << int(QScriptSyntaxCheckResult::Valid)
        << -1 << -1 << "";
    QTest::newRow("if (")
        << QString("if (\n") << int(QScriptSyntaxCheckResult::Intermediate)
        << 1 << 4 << "";
    QTest::newRow("if else")
        << QString("\nif else") << int(QScriptSyntaxCheckResult::Error)
        << 2 << 4 << "Expected `('";
    QTest::newRow("foo[")
        << QString("foo[") << int(QScriptSyntaxCheckResult::Error)
        << 1 << 4 << "";
    QTest::newRow("foo['bar']")
        << QString("foo['bar']") << int(QScriptSyntaxCheckResult::Valid)
        << -1 << -1 << "";

    QTest::newRow("/*")
        << QString("/*") << int(QScriptSyntaxCheckResult::Intermediate)
        << 1 << 1 << "Unclosed comment at end of file";
    QTest::newRow("/*\nMy comment")
        << QString("/*\nMy comment") << int(QScriptSyntaxCheckResult::Intermediate)
        << 1 << 1 << "Unclosed comment at end of file";
    QTest::newRow("/*\nMy comment */\nfoo = 10")
        << QString("/*\nMy comment */\nfoo = 10") << int(QScriptSyntaxCheckResult::Valid)
        << -1 << -1 << "";
    QTest::newRow("foo = 10 /*")
        << QString("foo = 10 /*") << int(QScriptSyntaxCheckResult::Intermediate)
        << -1 << -1 << "";
    QTest::newRow("foo = 10; /*")
        << QString("foo = 10; /*") << int(QScriptSyntaxCheckResult::Intermediate)
        << 1 << 11 << "Expected `end of file'";
    QTest::newRow("foo = 10 /* My comment */")
        << QString("foo = 10 /* My comment */") << int(QScriptSyntaxCheckResult::Valid)
        << -1 << -1 << "";

    QTest::newRow("/=/")
        << QString("/=/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
    QTest::newRow("/=/g")
        << QString("/=/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
    QTest::newRow("/a/")
        << QString("/a/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
    QTest::newRow("/a/g")
        << QString("/a/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
}

void tst_QScriptEngine::checkSyntax()
{
    QFETCH(QString, code);
    QFETCH(int, expectedState);
    QFETCH(int, errorLineNumber);
    QFETCH(int, errorColumnNumber);
    QFETCH(QString, errorMessage);

    QScriptSyntaxCheckResult result = QScriptEngine::checkSyntax(code);
    QCOMPARE(result.state(), QScriptSyntaxCheckResult::State(expectedState));
    QCOMPARE(result.errorLineNumber(), errorLineNumber);
    QCOMPARE(result.errorColumnNumber(), errorColumnNumber);
    QCOMPARE(result.errorMessage(), errorMessage);

    // assignment
    {
        QScriptSyntaxCheckResult copy = result;
        QCOMPARE(copy.state(), result.state());
        QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
        QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
        QCOMPARE(copy.errorMessage(), result.errorMessage());
    }
    {
        QScriptSyntaxCheckResult copy(result);
        QCOMPARE(copy.state(), result.state());
        QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
        QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
        QCOMPARE(copy.errorMessage(), result.errorMessage());
    }
}

void tst_QScriptEngine::canEvaluate_data()
{
    QTest::addColumn<QString>("code");
    QTest::addColumn<bool>("expectSuccess");

    QTest::newRow("") << QString("") << true;
    QTest::newRow("0") << QString("0") << true;
    QTest::newRow("!") << QString("!\n") << false;
    QTest::newRow("if (") << QString("if (\n") << false;
    QTest::newRow("if (10) //") << QString("if (10) //\n") << false;
    QTest::newRow("a = 1; if (") << QString("a = 1;\nif (\n") << false;
    QTest::newRow("./test.js") << QString("./test.js\n") << true;
    QTest::newRow("if (0) print(1)") << QString("if (0)\nprint(1)\n") << true;
    QTest::newRow("0 = ") << QString("0 = \n") << false;
    QTest::newRow("0 = 0") << QString("0 = 0\n") << true;
    QTest::newRow("foo[") << QString("foo[") << true; // automatic semicolon will be inserted
    QTest::newRow("foo[") << QString("foo[\n") << false;
    QTest::newRow("foo['bar']") << QString("foo['bar']") << true;

    QTest::newRow("/*") << QString("/*") << false;
    QTest::newRow("/*\nMy comment") << QString("/*\nMy comment") << false;
    QTest::newRow("/*\nMy comment */\nfoo = 10") << QString("/*\nMy comment */\nfoo = 10") << true;
    QTest::newRow("foo = 10 /*") << QString("foo = 10 /*") << false;
    QTest::newRow("foo = 10; /*") << QString("foo = 10; /*") << false;
    QTest::newRow("foo = 10 /* My comment */") << QString("foo = 10 /* My comment */") << true;

    QTest::newRow("/=/") << QString("/=/") << true;
    QTest::newRow("/=/g") << QString("/=/g") << true;
    QTest::newRow("/a/") << QString("/a/") << true;
    QTest::newRow("/a/g") << QString("/a/g") << true;
}

void tst_QScriptEngine::canEvaluate()
{
    QFETCH(QString, code);
    QFETCH(bool, expectSuccess);

    QScriptEngine eng;
    QCOMPARE(eng.canEvaluate(code), expectSuccess);
}

void tst_QScriptEngine::evaluate_data()
{
    QTest::addColumn<QString>("code");
    QTest::addColumn<int>("lineNumber");
    QTest::addColumn<bool>("expectHadError");
    QTest::addColumn<int>("expectErrorLineNumber");

    QTest::newRow("(newline)") << QString("\n") << -1 << false << -1;
    QTest::newRow("0 //")   << QString("0 //") << -1 << false << -1;
    QTest::newRow("/* */")   << QString("/* */") << -1 << false << -1;
    QTest::newRow("//") << QString("//") << -1 << false << -1;
    QTest::newRow("(spaces)")  << QString("  ") << -1 << false << -1;
    QTest::newRow("(empty)")   << QString("") << -1 << false << -1;
    QTest::newRow("0")     << QString("0")       << -1 << false << -1;
    QTest::newRow("0=1")   << QString("\n0=1;\n") << -1 << true  << 2;
    QTest::newRow("a=1")   << QString("a=1\n")   << -1 << false << -1;
    QTest::newRow("a=1;K") << QString("a=1;\nK") << -1 << true  << 2;

    QTest::newRow("f()") << QString("function f()\n"
                                    "{\n"
                                    "  var a;\n"
                                    "  var b=\";\n" // here's the error
                                    "}\n"
                                    "f();\n")
                         << -1 << true << 4;

    QTest::newRow("0")     << QString("0")       << 10 << false << -1;
    QTest::newRow("0=1")   << QString("\n\n0=1\n") << 10 << true  << 13;
    QTest::newRow("a=1")   << QString("a=1\n")   << 10 << false << -1;
    QTest::newRow("a=1;K") << QString("a=1;\n\nK") << 10 << true  << 12;

    QTest::newRow("f()") << QString("function f()\n"
                                    "{\n"
                                    "  var a;\n"
                                    "\n\n"
                                    "  var b=\";\n" // here's the error
                                    "}\n"
                                    "f();\n")
                         << 10 << true << 15;
    QTest::newRow("functionThatDoesntExist()")
        << QString(";\n;\n;\nfunctionThatDoesntExist()")
        << -1 << true << 4;
    QTest::newRow("for (var p in this) { continue labelThatDoesntExist; }")
        << QString("for (var p in this) {\ncontinue labelThatDoesntExist; }")
        << 4 << true << 5;
    QTest::newRow("duplicateLabel: { duplicateLabel: ; }")
        << QString("duplicateLabel: { duplicateLabel: ; }")
        << 12 << true << 12;

    QTest::newRow("/=/") << QString("/=/") << -1 << false << -1;
    QTest::newRow("/=/g") << QString("/=/g") << -1 << false << -1;
    QTest::newRow("/a/") << QString("/a/") << -1 << false << -1;
    QTest::newRow("/a/g") << QString("/a/g") << -1 << false << -1;
    QTest::newRow("/a/gim") << QString("/a/gim") << -1 << false << -1;
    QTest::newRow("/a/gimp") << QString("/a/gimp") << 1 << true << 1;
}

void tst_QScriptEngine::evaluate()
{
    QFETCH(QString, code);
    QFETCH(int, lineNumber);
    QFETCH(bool, expectHadError);
    QFETCH(int, expectErrorLineNumber);

    QScriptEngine eng;
    QScriptValue ret;
    if (lineNumber != -1)
        ret = eng.evaluate(code, /*fileName =*/QString(), lineNumber);
    else
        ret = eng.evaluate(code);
    QCOMPARE(eng.hasUncaughtException(), expectHadError);
    QCOMPARE(eng.uncaughtExceptionLineNumber(), expectErrorLineNumber);
    if (eng.hasUncaughtException() && ret.isError())
        QVERIFY(ret.property("lineNumber").strictlyEquals(QScriptValue(&eng, expectErrorLineNumber)));
    else
        QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
}

static QScriptValue eval_nested(QScriptContext *ctx, QScriptEngine *eng)
{
    QScriptValue result = eng->newObject();
    eng->evaluate("var bar = 'local';");
    result.setProperty("thisObjectIdBefore", ctx->thisObject().property("id"));
    QScriptValue evaluatedThisObject = eng->evaluate("this");
    result.setProperty("thisObjectIdAfter", ctx->thisObject().property("id"));
    result.setProperty("evaluatedThisObjectId", evaluatedThisObject.property("id"));
    result.setProperty("local_bar", eng->evaluate("bar"));

    return result;
}

// Tests that nested evaluate uses the "this" that was passed.
void tst_QScriptEngine::nestedEvaluate()
{
    QScriptEngine eng;
    QScriptValue fun = eng.newFunction(eval_nested);
    eng.globalObject().setProperty("fun", fun);
    // From JS function call
    {
        QScriptValue result = eng.evaluate("o = { id:'foo'}; o.fun = fun; o.fun()");
        QCOMPARE(result.property("local_bar").toString(), QString("local"));
        QCOMPARE(result.property("thisObjectIdBefore").toString(), QString("foo"));
        QCOMPARE(result.property("thisObjectIdAfter").toString(), QString("foo"));
        QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo"));
        QScriptValue bar = eng.evaluate("bar"); // Was introduced in local scope.
        QVERIFY(bar.isError());
        QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError")));
    }
    // From QScriptValue::call()
    {
        QScriptValue result = fun.call(eng.evaluate("p = { id:'foo' }") , QScriptValueList() );
        QCOMPARE(result.property("local_bar").toString(), QString("local"));
        QCOMPARE(result.property("thisObjectIdBefore").toString(), QString("foo"));
        QCOMPARE(result.property("thisObjectIdAfter").toString(), QString("foo"));
        QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo"));
        QScriptValue bar = eng.evaluate("bar");
        QVERIFY(bar.isError());
        QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError")));
    }
}

void tst_QScriptEngine::uncaughtException()
{
    QScriptEngine eng;
    QScriptValue fun = eng.newFunction(myFunction);
    QScriptValue throwFun = eng.newFunction(myThrowingFunction);
    for (int x = -1; x < 2; ++x) {
        {
            QScriptValue ret = eng.evaluate("a = 10;\nb = 20;\n0 = 0;\n", /*fileName=*/QString(), /*lineNumber=*/x);
            QVERIFY(eng.hasUncaughtException());
            QCOMPARE(eng.uncaughtExceptionLineNumber(), x+2);
            QVERIFY(eng.uncaughtException().strictlyEquals(ret));
            (void)ret.toString();
            QVERIFY(eng.hasUncaughtException());
            QVERIFY(eng.uncaughtException().strictlyEquals(ret));
            QVERIFY(fun.call().isNull());
            QVERIFY(eng.hasUncaughtException());
            QCOMPARE(eng.uncaughtExceptionLineNumber(), x+2);
            QVERIFY(eng.uncaughtException().strictlyEquals(ret));
            eng.clearExceptions();
            QVERIFY(!eng.hasUncaughtException());
            QCOMPARE(eng.uncaughtExceptionLineNumber(), -1);
            QVERIFY(!eng.uncaughtException().isValid());

            eng.evaluate("2 = 3");
            QVERIFY(eng.hasUncaughtException());
            QScriptValue ret2 = throwFun.call();
            QVERIFY(ret2.isError());
            QVERIFY(eng.hasUncaughtException());
            QVERIFY(eng.uncaughtException().strictlyEquals(ret2));
            QCOMPARE(eng.uncaughtExceptionLineNumber(), 0);
            eng.clearExceptions();
            QVERIFY(!eng.hasUncaughtException());
            eng.evaluate("1 + 2");
            QVERIFY(!eng.hasUncaughtException());
        }
        {
            QScriptValue ret = eng.evaluate("a = 10");
            QVERIFY(!eng.hasUncaughtException());
            QVERIFY(!eng.uncaughtException().isValid());
        }
        {
            QScriptValue ret = eng.evaluate("1 = 2");
            QVERIFY(eng.hasUncaughtException());
            eng.clearExceptions();
            QVERIFY(!eng.hasUncaughtException());
        }
        {
            eng.globalObject().setProperty("throwFun", throwFun);
            eng.evaluate("1;\nthrowFun();");
            QVERIFY(eng.hasUncaughtException());
            QCOMPARE(eng.uncaughtExceptionLineNumber(), 2);
            eng.clearExceptions();
            QVERIFY(!eng.hasUncaughtException());
        }
    }
}

void tst_QScriptEngine::errorMessage_QT679()
{
    QScriptEngine engine;
    engine.globalObject().setProperty("foo", 15);
    QScriptValue error = engine.evaluate("'hello world';\nfoo.bar.blah");
    QVERIFY(error.isError());
    // The exact message is back-end specific and subject to change.
    QCOMPARE(error.toString(), QString::fromLatin1("TypeError: Result of expression 'foo.bar' [undefined] is not an object."));
}

struct Foo {
public:
    int x, y;
    Foo() : x(-1), y(-1) { }
};

Q_DECLARE_METATYPE(Foo)
Q_DECLARE_METATYPE(Foo*)

void tst_QScriptEngine::getSetDefaultPrototype_int()
{
    QScriptEngine eng;

    QScriptValue object = eng.newObject();
    QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false);
    eng.setDefaultPrototype(qMetaTypeId<int>(), object);
    QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).strictlyEquals(object), true);
    QScriptValue value = eng.newVariant(int(123));
    QCOMPARE(value.prototype().isObject(), true);
    QCOMPARE(value.prototype().strictlyEquals(object), true);

    eng.setDefaultPrototype(qMetaTypeId<int>(), QScriptValue());
    QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false);
    QScriptValue value2 = eng.newVariant(int(123));
    QCOMPARE(value2.prototype().strictlyEquals(object), false);
}

void tst_QScriptEngine::getSetDefaultPrototype_customType()
{
    QScriptEngine eng;

    QScriptValue object = eng.newObject();
    QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false);
    eng.setDefaultPrototype(qMetaTypeId<Foo>(), object);
    QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(object), true);
    QScriptValue value = eng.newVariant(qVariantFromValue(Foo()));
    QCOMPARE(value.prototype().isObject(), true);
    QCOMPARE(value.prototype().strictlyEquals(object), true);

    eng.setDefaultPrototype(qMetaTypeId<Foo>(), QScriptValue());
    QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false);
    QScriptValue value2 = eng.newVariant(qVariantFromValue(Foo()));
    QCOMPARE(value2.prototype().strictlyEquals(object), false);
}

static QScriptValue fooToScriptValue(QScriptEngine *eng, const Foo &foo)
{
    QScriptValue obj = eng->newObject();
    obj.setProperty("x", QScriptValue(eng, foo.x));
    obj.setProperty("y", QScriptValue(eng, foo.y));
    return obj;
}

static void fooFromScriptValue(const QScriptValue &value, Foo &foo)
{
    foo.x = value.property("x").toInt32();
    foo.y = value.property("y").toInt32();
}

static QScriptValue fooToScriptValueV2(QScriptEngine *eng, const Foo &foo)
{
    return QScriptValue(eng, foo.x);
}

static void fooFromScriptValueV2(const QScriptValue &value, Foo &foo)
{
    foo.x = value.toInt32();
}

Q_DECLARE_METATYPE(QLinkedList<QString>)
Q_DECLARE_METATYPE(QList<Foo>)
Q_DECLARE_METATYPE(QVector<QChar>)
Q_DECLARE_METATYPE(QStack<int>)
Q_DECLARE_METATYPE(QQueue<char>)
Q_DECLARE_METATYPE(QLinkedList<QStack<int> >)

void tst_QScriptEngine::valueConversion_basic()
{
    QScriptEngine eng;
    {
        QScriptValue num = qScriptValueFromValue(&eng, 123);
        QCOMPARE(num.isNumber(), true);
        QCOMPARE(num.strictlyEquals(QScriptValue(&eng, 123)), true);

        int inum = qScriptValueToValue<int>(num);
        QCOMPARE(inum, 123);

        QString snum = qScriptValueToValue<QString>(num);
        QCOMPARE(snum, QLatin1String("123"));
    }
    {
        QScriptValue num = eng.toScriptValue(123);
        QCOMPARE(num.isNumber(), true);
        QCOMPARE(num.strictlyEquals(QScriptValue(&eng, 123)), true);

        int inum = eng.fromScriptValue<int>(num);
        QCOMPARE(inum, 123);

        QString snum = eng.fromScriptValue<QString>(num);
        QCOMPARE(snum, QLatin1String("123"));
    }
    {
        QScriptValue num(&eng, 123);
        QCOMPARE(qScriptValueToValue<char>(num), char(123));
        QCOMPARE(qScriptValueToValue<unsigned char>(num), (unsigned char)(123));
        QCOMPARE(qScriptValueToValue<short>(num), short(123));
        QCOMPARE(qScriptValueToValue<unsigned short>(num), (unsigned short)(123));
        QCOMPARE(qScriptValueToValue<float>(num), float(123));
        QCOMPARE(qScriptValueToValue<double>(num), double(123));
        QCOMPARE(qScriptValueToValue<qlonglong>(num), qlonglong(123));
        QCOMPARE(qScriptValueToValue<qulonglong>(num), qulonglong(123));
    }
    {
        QScriptValue num(123);
        QCOMPARE(qScriptValueToValue<char>(num), char(123));
        QCOMPARE(qScriptValueToValue<unsigned char>(num), (unsigned char)(123));
        QCOMPARE(qScriptValueToValue<short>(num), short(123));
        QCOMPARE(qScriptValueToValue<unsigned short>(num), (unsigned short)(123));
        QCOMPARE(qScriptValueToValue<float>(num), float(123));
        QCOMPARE(qScriptValueToValue<double>(num), double(123));
        QCOMPARE(qScriptValueToValue<qlonglong>(num), qlonglong(123));
        QCOMPARE(qScriptValueToValue<qulonglong>(num), qulonglong(123));
    }

    {
        QScriptValue num = qScriptValueFromValue(&eng, Q_INT64_C(0x100000000));
        QCOMPARE(qScriptValueToValue<qlonglong>(num), Q_INT64_C(0x100000000));
        QCOMPARE(qScriptValueToValue<qulonglong>(num), Q_UINT64_C(0x100000000));
    }

    {
        QChar c = QLatin1Char('c');
        QScriptValue str = QScriptValue(&eng, "ciao");
        QCOMPARE(qScriptValueToValue<QChar>(str), c);
        QScriptValue code = QScriptValue(&eng, c.unicode());
        QCOMPARE(qScriptValueToValue<QChar>(code), c);
        QCOMPARE(qScriptValueToValue<QChar>(qScriptValueFromValue(&eng, c)), c);
    }
}

void tst_QScriptEngine::valueConversion_customType()
{
    QScriptEngine eng;
    {
        // a type that we don't have built-in conversion of
        // (it's stored as a variant)
        QTime tm(1, 2, 3, 4);
        QScriptValue val = qScriptValueFromValue(&eng, tm);
        QCOMPARE(qScriptValueToValue<QTime>(val), tm);
    }

    {
        Foo foo;
        foo.x = 12;
        foo.y = 34;
        QScriptValue fooVal = qScriptValueFromValue(&eng, foo);
        QCOMPARE(fooVal.isVariant(), true);

        Foo foo2 = qScriptValueToValue<Foo>(fooVal);
        QCOMPARE(foo2.x, foo.x);
        QCOMPARE(foo2.y, foo.y);
    }

    qScriptRegisterMetaType<Foo>(&eng, fooToScriptValue, fooFromScriptValue);

    {
        Foo foo;
        foo.x = 12;
        foo.y = 34;
        QScriptValue fooVal = qScriptValueFromValue(&eng, foo);
        QCOMPARE(fooVal.isObject(), true);
        QVERIFY(fooVal.prototype().strictlyEquals(eng.evaluate("Object.prototype")));
        QCOMPARE(fooVal.property("x").strictlyEquals(QScriptValue(&eng, 12)), true);
        QCOMPARE(fooVal.property("y").strictlyEquals(QScriptValue(&eng, 34)), true);
        fooVal.setProperty("x", QScriptValue(&eng, 56));
        fooVal.setProperty("y", QScriptValue(&eng, 78));

        Foo foo2 = qScriptValueToValue<Foo>(fooVal);
        QCOMPARE(foo2.x, 56);
        QCOMPARE(foo2.y, 78);

        QScriptValue fooProto = eng.newObject();
        eng.setDefaultPrototype(qMetaTypeId<Foo>(), fooProto);
        QScriptValue fooVal2 = qScriptValueFromValue(&eng, foo2);
        QVERIFY(fooVal2.prototype().strictlyEquals(fooProto));
        QVERIFY(fooVal2.property("x").strictlyEquals(QScriptValue(&eng, 56)));
        QVERIFY(fooVal2.property("y").strictlyEquals(QScriptValue(&eng, 78)));
    }
}

void tst_QScriptEngine::valueConversion_sequence()
{
    QScriptEngine eng;
    qScriptRegisterSequenceMetaType<QLinkedList<QString> >(&eng);

    {
        QLinkedList<QString> lst;
        lst << QLatin1String("foo") << QLatin1String("bar");
        QScriptValue lstVal = qScriptValueFromValue(&eng, lst);
        QCOMPARE(lstVal.isArray(), true);
        QCOMPARE(lstVal.property("length").toInt32(), 2);
        QCOMPARE(lstVal.property("0").isString(), true);
        QCOMPARE(lstVal.property("0").toString(), QLatin1String("foo"));
        QCOMPARE(lstVal.property("1").isString(), true);
        QCOMPARE(lstVal.property("1").toString(), QLatin1String("bar"));
    }

    qScriptRegisterSequenceMetaType<QList<Foo> >(&eng);
    qScriptRegisterSequenceMetaType<QStack<int> >(&eng);
    qScriptRegisterSequenceMetaType<QVector<QChar> >(&eng);
    qScriptRegisterSequenceMetaType<QQueue<char> >(&eng);
    qScriptRegisterSequenceMetaType<QLinkedList<QStack<int> > >(&eng);

    {
        QLinkedList<QStack<int> > lst;
        QStack<int> first; first << 13 << 49; lst << first;
        QStack<int> second; second << 99999;lst << second;
        QScriptValue lstVal = qScriptValueFromValue(&eng, lst);
        QCOMPARE(lstVal.isArray(), true);
        QCOMPARE(lstVal.property("length").toInt32(), 2);
        QCOMPARE(lstVal.property("0").isArray(), true);
        QCOMPARE(lstVal.property("0").property("length").toInt32(), 2);
        QCOMPARE(lstVal.property("0").property("0").toInt32(), first.at(0));
        QCOMPARE(lstVal.property("0").property("1").toInt32(), first.at(1));
        QCOMPARE(lstVal.property("1").isArray(), true);
        QCOMPARE(lstVal.property("1").property("length").toInt32(), 1);
        QCOMPARE(lstVal.property("1").property("0").toInt32(), second.at(0));
        QCOMPARE(qscriptvalue_cast<QStack<int> >(lstVal.property("0")), first);
        QCOMPARE(qscriptvalue_cast<QStack<int> >(lstVal.property("1")), second);
        QCOMPARE(qscriptvalue_cast<QLinkedList<QStack<int> > >(lstVal), lst);
    }

    // pointers
    {
        Foo foo;
        {
            QScriptValue v = qScriptValueFromValue(&eng, &foo);
            Foo *pfoo = qscriptvalue_cast<Foo*>(v);
            QCOMPARE(pfoo, &foo);
        }
        {
            Foo *pfoo = 0;
            QScriptValue v = qScriptValueFromValue(&eng, pfoo);
            QCOMPARE(v.isNull(), true);
            QVERIFY(qscriptvalue_cast<Foo*>(v) == 0);
        }
    }

    // QList<int> and QObjectList should be converted from/to arrays by default
    {
        QList<int> lst;
        lst << 1 << 2 << 3;
        QScriptValue val = qScriptValueFromValue(&eng, lst);
        QVERIFY(val.isArray());
        QCOMPARE(val.property("length").toInt32(), lst.size());
        QCOMPARE(val.property(0).toInt32(), lst.at(0));
        QCOMPARE(val.property(1).toInt32(), lst.at(1));
        QCOMPARE(val.property(2).toInt32(), lst.at(2));

        QCOMPARE(qscriptvalue_cast<QList<int> >(val), lst);
    }
    {
        QObjectList lst;
        lst << this;
        QScriptValue val = qScriptValueFromValue(&eng, lst);
        QVERIFY(val.isArray());
        QCOMPARE(val.property("length").toInt32(), lst.size());
        QCOMPARE(val.property(0).toQObject(), (QObject *)this);

        QCOMPARE(qscriptvalue_cast<QObjectList>(val), lst);
    }
}

void tst_QScriptEngine::valueConversion_QVariant()
{
    QScriptEngine eng;
    // qScriptValueFromValue() should be "smart" when the argument is a QVariant
    {
        QScriptValue val = qScriptValueFromValue(&eng, QVariant());
        QVERIFY(!val.isVariant());
        QVERIFY(val.isUndefined());
    }
    // Checking nested QVariants
    {
        QVariant tmp1;
        QVariant tmp2(QMetaType::QVariant, &tmp1);
        QVERIFY(QMetaType::Type(tmp2.type()) == QMetaType::QVariant);

        QScriptValue val1 = qScriptValueFromValue(&eng, tmp1);
        QScriptValue val2 = qScriptValueFromValue(&eng, tmp2);
        QVERIFY(val1.isValid());
        QVERIFY(val2.isValid());
        QVERIFY(val1.isUndefined());
        QVERIFY(!val2.isUndefined());
        QVERIFY(!val1.isVariant());
        QVERIFY(val2.isVariant());
    }
    {
        QVariant tmp1(123);
        QVariant tmp2(QMetaType::QVariant, &tmp1);
        QVariant tmp3(QMetaType::QVariant, &tmp2);
        QVERIFY(QMetaType::Type(tmp1.type()) == QMetaType::Int);
        QVERIFY(QMetaType::Type(tmp2.type()) == QMetaType::QVariant);
        QVERIFY(QMetaType::Type(tmp3.type()) == QMetaType::QVariant);

        QScriptValue val1 = qScriptValueFromValue(&eng, tmp2);
        QScriptValue val2 = qScriptValueFromValue(&eng, tmp3);
        QVERIFY(val1.isValid());
        QVERIFY(val2.isValid());
        QVERIFY(val1.isVariant());
        QVERIFY(val2.isVariant());
        QVERIFY(val1.toVariant().toInt() == 123);
        QVERIFY(qScriptValueFromValue(&eng, val2.toVariant()).toVariant().toInt() == 123);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, QVariant(true));
        QVERIFY(!val.isVariant());
        QVERIFY(val.isBoolean());
        QCOMPARE(val.toBoolean(), true);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, QVariant(int(123)));
        QVERIFY(!val.isVariant());
        QVERIFY(val.isNumber());
        QCOMPARE(val.toNumber(), qsreal(123));
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, QVariant(qsreal(1.25)));
        QVERIFY(!val.isVariant());
        QVERIFY(val.isNumber());
        QCOMPARE(val.toNumber(), qsreal(1.25));
    }
    {
        QString str = QString::fromLatin1("ciao");
        QScriptValue val = qScriptValueFromValue(&eng, QVariant(str));
        QVERIFY(!val.isVariant());
        QVERIFY(val.isString());
        QCOMPARE(val.toString(), str);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, qVariantFromValue((QObject*)this));
        QVERIFY(!val.isVariant());
        QVERIFY(val.isQObject());
        QCOMPARE(val.toQObject(), (QObject*)this);
    }
    {
        QVariant var = qVariantFromValue(QPoint(123, 456));
        QScriptValue val = qScriptValueFromValue(&eng, var);
        QVERIFY(val.isVariant());
        QCOMPARE(val.toVariant(), var);
    }

    QCOMPARE(qscriptvalue_cast<QVariant>(QScriptValue(123)), QVariant(123));
}

void tst_QScriptEngine::valueConversion_hooliganTask248802()
{
    QScriptEngine eng;
    qScriptRegisterMetaType<Foo>(&eng, fooToScriptValueV2, fooFromScriptValueV2);
    {
        QScriptValue num(&eng, 123);
        Foo foo = qScriptValueToValue<Foo>(num);
        QCOMPARE(foo.x, 123);
    }
    {
        QScriptValue num(123);
        Foo foo = qScriptValueToValue<Foo>(num);
        QCOMPARE(foo.x, -1);
    }
    {
        QScriptValue str(&eng, "123");
        Foo foo = qScriptValueToValue<Foo>(str);
        QCOMPARE(foo.x, 123);
    }

}

void tst_QScriptEngine::valueConversion_basic2()
{
    QScriptEngine eng;
    // more built-in types
    {
        QScriptValue val = qScriptValueFromValue(&eng, uint(123));
        QVERIFY(val.isNumber());
        QCOMPARE(val.toInt32(), 123);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, qulonglong(123));
        QVERIFY(val.isNumber());
        QCOMPARE(val.toInt32(), 123);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, float(123));
        QVERIFY(val.isNumber());
        QCOMPARE(val.toInt32(), 123);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, short(123));
        QVERIFY(val.isNumber());
        QCOMPARE(val.toInt32(), 123);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, ushort(123));
        QVERIFY(val.isNumber());
        QCOMPARE(val.toInt32(), 123);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, char(123));
        QVERIFY(val.isNumber());
        QCOMPARE(val.toInt32(), 123);
    }
    {
        QScriptValue val = qScriptValueFromValue(&eng, uchar(123));
        QVERIFY(val.isNumber());
        QCOMPARE(val.toInt32(), 123);
    }
}

void tst_QScriptEngine::valueConversion_dateTime()
{
    QScriptEngine eng;
    {
        QDateTime in = QDateTime::currentDateTime();
        QScriptValue val = qScriptValueFromValue(&eng, in);
        QVERIFY(val.isDate());
        QCOMPARE(val.toDateTime(), in);
    }
    {
        QDate in = QDate::currentDate();
        QScriptValue val = qScriptValueFromValue(&eng, in);
        QVERIFY(val.isDate());
        QCOMPARE(val.toDateTime().date(), in);
    }
}

void tst_QScriptEngine::valueConversion_regExp()
{
    QScriptEngine eng;
    {
        QRegExp in = QRegExp("foo");
        QScriptValue val = qScriptValueFromValue(&eng, in);
        QVERIFY(val.isRegExp());
        QRegExp out = val.toRegExp();
        QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::patternSyntax (always uses RegExp2)", Continue);
        QCOMPARE(out.patternSyntax(), in.patternSyntax());
        QCOMPARE(out.pattern(), in.pattern());
        QCOMPARE(out.caseSensitivity(), in.caseSensitivity());
        QCOMPARE(out.isMinimal(), in.isMinimal());
    }
    {
        QRegExp in = QRegExp("foo", Qt::CaseSensitive, QRegExp::RegExp2);
        QScriptValue val = qScriptValueFromValue(&eng, in);
        QVERIFY(val.isRegExp());
        QCOMPARE(val.toRegExp(), in);
    }
    {
        QRegExp in = QRegExp("foo");
        in.setMinimal(true);
        QScriptValue val = qScriptValueFromValue(&eng, in);
        QVERIFY(val.isRegExp());
        QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::minimal (always false)", Continue);
        QCOMPARE(val.toRegExp().isMinimal(), in.isMinimal());
    }
}

void tst_QScriptEngine::valueConversion_long()
{
    QScriptEngine eng;
    {
        QScriptValue num(&eng, 123);
        QCOMPARE(qscriptvalue_cast<long>(num), long(123));
        QCOMPARE(qscriptvalue_cast<ulong>(num), ulong(123));
    }
    {
        QScriptValue num(456);
        QCOMPARE(qscriptvalue_cast<long>(num), long(456));
        QCOMPARE(qscriptvalue_cast<ulong>(num), ulong(456));
    }
    {
        QScriptValue str(&eng, "123");
        QCOMPARE(qscriptvalue_cast<long>(str), long(123));
        QCOMPARE(qscriptvalue_cast<ulong>(str), ulong(123));
    }
    {
        QScriptValue str("456");
        QCOMPARE(qscriptvalue_cast<long>(str), long(456));
        QCOMPARE(qscriptvalue_cast<ulong>(str), ulong(456));
    }
    {
        QScriptValue num = qScriptValueFromValue<long>(&eng, long(123));
        QCOMPARE(num.toInt32(), 123);
    }
    {
        QScriptValue num = qScriptValueFromValue<ulong>(&eng, ulong(456));
        QCOMPARE(num.toInt32(), 456);
    }
}

void tst_QScriptEngine::qScriptValueFromValue_noEngine()
{
    QVERIFY(!qScriptValueFromValue(0, 123).isValid());
    QVERIFY(!qScriptValueFromValue(0, QVariant(123)).isValid());
}

static QScriptValue __import__(QScriptContext *ctx, QScriptEngine *eng)
{
    return eng->importExtension(ctx->argument(0).toString());
}

void tst_QScriptEngine::importExtension()
{
    QStringList libPaths = QCoreApplication::instance()->libraryPaths();
    QCoreApplication::instance()->setLibraryPaths(QStringList() << SRCDIR);

    QStringList availableExtensions;
    {
        QScriptEngine eng;
        QVERIFY(eng.importedExtensions().isEmpty());
        QStringList ret = eng.availableExtensions();
        QCOMPARE(ret.size(), 4);
        QCOMPARE(ret.at(0), QString::fromLatin1("com"));
        QCOMPARE(ret.at(1), QString::fromLatin1("com.trolltech"));
        QCOMPARE(ret.at(2), QString::fromLatin1("com.trolltech.recursive"));
        QCOMPARE(ret.at(3), QString::fromLatin1("com.trolltech.syntaxerror"));
        availableExtensions = ret;
    }

    // try to import something that doesn't exist
    {
        QScriptEngine eng;
        QScriptValue ret = eng.importExtension("this.extension.does.not.exist");
        QCOMPARE(eng.hasUncaughtException(), true);
        QCOMPARE(ret.isError(), true);
        QCOMPARE(ret.toString(), QString::fromLatin1("Error: Unable to import this.extension.does.not.exist: no such extension"));
    }

    {
        QScriptEngine eng;
        for (int x = 0; x < 2; ++x) {
            QCOMPARE(eng.globalObject().property("com").isValid(), x == 1);
            QScriptValue ret = eng.importExtension("com.trolltech");
            QCOMPARE(eng.hasUncaughtException(), false);
            QCOMPARE(ret.isUndefined(), true);

            QScriptValue com = eng.globalObject().property("com");
            QCOMPARE(com.isObject(), true);
            QCOMPARE(com.property("wasDefinedAlready")
                     .strictlyEquals(QScriptValue(&eng, false)), true);
            QCOMPARE(com.property("name")
                     .strictlyEquals(QScriptValue(&eng, "com")), true);
            QCOMPARE(com.property("level")
                     .strictlyEquals(QScriptValue(&eng, 1)), true);
            QVERIFY(com.property("originalPostInit").isUndefined());
            QVERIFY(com.property("postInitCallCount").strictlyEquals(1));

            QScriptValue trolltech = com.property("trolltech");
            QCOMPARE(trolltech.isObject(), true);
            QCOMPARE(trolltech.property("wasDefinedAlready")
                     .strictlyEquals(QScriptValue(&eng, false)), true);
            QCOMPARE(trolltech.property("name")
                     .strictlyEquals(QScriptValue(&eng, "com.trolltech")), true);
            QCOMPARE(trolltech.property("level")
                     .strictlyEquals(QScriptValue(&eng, 2)), true);
            QVERIFY(trolltech.property("originalPostInit").isUndefined());
            QVERIFY(trolltech.property("postInitCallCount").strictlyEquals(1));
        }
        QStringList imp = eng.importedExtensions();
        QCOMPARE(imp.size(), 2);
        QCOMPARE(imp.at(0), QString::fromLatin1("com"));
        QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
        QCOMPARE(eng.availableExtensions(), availableExtensions);
    }

    // recursive import should throw an error
    {
        QScriptEngine eng;
        QVERIFY(eng.importedExtensions().isEmpty());
        eng.globalObject().setProperty("__import__", eng.newFunction(__import__));
        QScriptValue ret = eng.importExtension("com.trolltech.recursive");
        QCOMPARE(eng.hasUncaughtException(), true);
        QVERIFY(ret.isError());
        QCOMPARE(ret.toString(), QString::fromLatin1("Error: recursive import of com.trolltech.recursive"));
        QStringList imp = eng.importedExtensions();
        QCOMPARE(imp.size(), 2);
        QCOMPARE(imp.at(0), QString::fromLatin1("com"));
        QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
        QCOMPARE(eng.availableExtensions(), availableExtensions);
    }

    {
        QScriptEngine eng;
        eng.globalObject().setProperty("__import__", eng.newFunction(__import__));
        for (int x = 0; x < 2; ++x) {
            if (x == 0)
                QVERIFY(eng.importedExtensions().isEmpty());
            QScriptValue ret = eng.importExtension("com.trolltech.syntaxerror");
            QVERIFY(eng.hasUncaughtException());
            QEXPECT_FAIL("", "JSC throws syntax error eagerly", Continue);
            QCOMPARE(eng.uncaughtExceptionLineNumber(), 4);
            QVERIFY(ret.isError());
            QVERIFY(ret.toString().contains(QLatin1String("SyntaxError")));
        }
        QStringList imp = eng.importedExtensions();
        QCOMPARE(imp.size(), 2);
        QCOMPARE(imp.at(0), QString::fromLatin1("com"));
        QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
        QCOMPARE(eng.availableExtensions(), availableExtensions);
    }

    QCoreApplication::instance()->setLibraryPaths(libPaths);
}

#if 0 //The native C++ stack overflow before the JS stack
static QScriptValue recurse(QScriptContext *ctx, QScriptEngine *eng)
{
    Q_UNUSED(eng);
    return ctx->callee().call();
}

static QScriptValue recurse2(QScriptContext *ctx, QScriptEngine *eng)
{
    Q_UNUSED(eng);
    return ctx->callee().construct();
}
#endif

void tst_QScriptEngine::infiniteRecursion()
{
    // Infinite recursion in JS should cause the VM to throw an error
    // when the JS stack is exhausted.
    // The exact error is back-end specific and subject to change.
    const QString stackOverflowError = QString::fromLatin1("RangeError: Maximum call stack size exceeded.");
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("function foo() { foo(); }; foo();");
        QCOMPARE(ret.isError(), true);
        QCOMPARE(ret.toString(), stackOverflowError);
    }
#if 0 //The native C++ stack overflow before the JS stack
    {
        QScriptValue fun = eng.newFunction(recurse);
        QScriptValue ret = fun.call();
        QCOMPARE(ret.isError(), true);
        QCOMPARE(ret.toString(), stackOverflowError);
    }
    {
        QScriptValue fun = eng.newFunction(recurse2);
        QScriptValue ret = fun.construct();
        QCOMPARE(ret.isError(), true);
        QCOMPARE(ret.toString(), stackOverflowError);
    }
#endif
}

struct Bar {
    int a;
};

struct Baz : public Bar {
    int b;
};

Q_DECLARE_METATYPE(Bar*)
Q_DECLARE_METATYPE(Baz*)

Q_DECLARE_METATYPE(QGradient)
Q_DECLARE_METATYPE(QGradient*)
Q_DECLARE_METATYPE(QLinearGradient)

class Zoo : public QObject
{
    Q_OBJECT
public:
    Zoo() { }
public slots:
    Baz *toBaz(Bar *b) { return reinterpret_cast<Baz*>(b); }
};

void tst_QScriptEngine::castWithPrototypeChain()
{
    QScriptEngine eng;
    Bar bar;
    Baz baz;
    QScriptValue barProto = qScriptValueFromValue(&eng, &bar);
    QScriptValue bazProto = qScriptValueFromValue(&eng, &baz);
    eng.setDefaultPrototype(qMetaTypeId<Bar*>(), barProto);
    eng.setDefaultPrototype(qMetaTypeId<Baz*>(), bazProto);

    Baz baz2;
    baz2.a = 123;
    baz2.b = 456;
    QScriptValue baz2Value = qScriptValueFromValue(&eng, &baz2);
    {
        // qscriptvalue_cast() does magic; if the QScriptValue contains
        // t of type T, and the target type is T*, &t is returned.
        Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
        QVERIFY(pbaz != 0);
        QCOMPARE(pbaz->b, baz2.b);

        Zoo zoo;
        QScriptValue scriptZoo = eng.newQObject(&zoo);
        QScriptValue toBaz = scriptZoo.property("toBaz");
        QVERIFY(toBaz.isFunction());

        // no relation between Bar and Baz's proto --> casting fails
        {
            Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
            QVERIFY(pbar == 0);
        }

        {
            QScriptValue ret = toBaz.call(scriptZoo, QScriptValueList() << baz2Value);
            QVERIFY(ret.isError());
            QCOMPARE(ret.toString(), QLatin1String("TypeError: incompatible type of argument(s) in call to toBaz(); candidates were\n    toBaz(Bar*)"));
        }

        // establish chain -- now casting should work
        // Why? because qscriptvalue_cast() does magic again.
        // It the instance itself is not of type T, qscriptvalue_cast()
        // searches the prototype chain for T, and if it finds one, it infers
        // that the instance can also be casted to that type. This cast is
        // _not_ safe and thus relies on the developer doing the right thing.
        // This is an undocumented feature to enable qscriptvalue_cast() to
        // be used by prototype functions to cast the JS this-object to C++.
        bazProto.setPrototype(barProto);

        {
            Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
            QVERIFY(pbar != 0);
            QCOMPARE(pbar->a, baz2.a);
        }

        {
            QScriptValue ret = toBaz.call(scriptZoo, QScriptValueList() << baz2Value);
            QVERIFY(!ret.isError());
            QCOMPARE(qscriptvalue_cast<Baz*>(ret), pbaz);
        }
    }

    bazProto.setPrototype(barProto.prototype()); // kill chain
    {
        Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
        QVERIFY(pbaz != 0);
        // should not work anymore
        Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
        QVERIFY(pbar == 0);
    }

    bazProto.setPrototype(eng.newQObject(this));
    {
        Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
        QVERIFY(pbaz != 0);
        // should not work now either
        Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
        QVERIFY(pbar == 0);
    }

    {
        QScriptValue b = qScriptValueFromValue(&eng, QBrush());
        b.setPrototype(barProto);
        // this shows that a "wrong" cast is possible, if you
        // don't play by the rules (the pointer is actually a QBrush*)...
        Bar *pbar = qscriptvalue_cast<Bar*>(b);
        QVERIFY(pbar != 0);
    }

    {
        QScriptValue gradientProto = qScriptValueFromValue(&eng, QGradient());
        QScriptValue linearGradientProto = qScriptValueFromValue(&eng, QLinearGradient());
        linearGradientProto.setPrototype(gradientProto);
        QLinearGradient lg(10, 20, 30, 40);
        QScriptValue linearGradient = qScriptValueFromValue(&eng, lg);
        {
            QGradient *pgrad = qscriptvalue_cast<QGradient*>(linearGradient);
            QVERIFY(pgrad == 0);
        }
        linearGradient.setPrototype(linearGradientProto);
        {
            QGradient *pgrad = qscriptvalue_cast<QGradient*>(linearGradient);
            QVERIFY(pgrad != 0);
            QCOMPARE(pgrad->type(), QGradient::LinearGradient);
            QLinearGradient *plingrad = static_cast<QLinearGradient*>(pgrad);
            QCOMPARE(plingrad->start(), lg.start());
            QCOMPARE(plingrad->finalStop(), lg.finalStop());
        }
    }
}

class Klazz : public QWidget,
              public QStandardItem,
              public QGraphicsItem
{
    Q_OBJECT
public:
    Klazz(QWidget *parent = 0) : QWidget(parent) { }
    virtual QRectF boundingRect() const { return QRectF(); }
    virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { }
};

Q_DECLARE_METATYPE(Klazz*)
Q_DECLARE_METATYPE(QStandardItem*)

void tst_QScriptEngine::castWithMultipleInheritance()
{
    QScriptEngine eng;
    Klazz klz;
    QScriptValue v = eng.newQObject(&klz);

    QCOMPARE(qscriptvalue_cast<Klazz*>(v), &klz);
    QCOMPARE(qscriptvalue_cast<QWidget*>(v), (QWidget *)&klz);
    QCOMPARE(qscriptvalue_cast<QObject*>(v), (QObject *)&klz);
    QCOMPARE(qscriptvalue_cast<QStandardItem*>(v), (QStandardItem *)&klz);
    QCOMPARE(qscriptvalue_cast<QGraphicsItem*>(v), (QGraphicsItem *)&klz);
}

void tst_QScriptEngine::collectGarbage()
{
    QScriptEngine eng;
    eng.evaluate("a = new Object(); a = new Object(); a = new Object()");
    QScriptValue a = eng.newObject();
    a = eng.newObject();
    a = eng.newObject();
    QPointer<QObject> ptr = new QObject();
    QVERIFY(ptr != 0);
    (void)eng.newQObject(ptr, QScriptEngine::ScriptOwnership);
    collectGarbage_helper(eng);
    QSKIP("This test does not work reliably"); // (maybe some pointers are still on the stack somewhere)
    QVERIFY(ptr == 0);
}

void tst_QScriptEngine::reportAdditionalMemoryCost()
{
    QScriptEngine eng;
    // There isn't any reliable way to test whether calling
    // this function affects garbage collection responsiveness;
    // the best we can do is call it with a few different values.
    for (int x = 0; x < 1000; ++x) {
        eng.reportAdditionalMemoryCost(0);
        eng.reportAdditionalMemoryCost(10);
        eng.reportAdditionalMemoryCost(1000);
        eng.reportAdditionalMemoryCost(10000);
        eng.reportAdditionalMemoryCost(100000);
        eng.reportAdditionalMemoryCost(1000000);
        eng.reportAdditionalMemoryCost(10000000);
        eng.reportAdditionalMemoryCost(-1);
        eng.reportAdditionalMemoryCost(-1000);
        QScriptValue obj = eng.newObject();
        eng.collectGarbage();
    }
}

void tst_QScriptEngine::gcWithNestedDataStructure()
{
    // The GC must be able to traverse deeply nested objects, otherwise this
    // test would crash.
    QScriptEngine eng;
    eng.evaluate(
        "function makeList(size)"
        "{"
        "  var head = { };"
        "  var l = head;"
        "  for (var i = 0; i < size; ++i) {"
        "    l.data = i + \"\";"
        "    l.next = { }; l = l.next;"
        "  }"
        "  l.next = null;"
        "  return head;"
        "}");
    QCOMPARE(eng.hasUncaughtException(), false);
    const int size = 200;
    QScriptValue head = eng.evaluate(QString::fromLatin1("makeList(%0)").arg(size));
    QCOMPARE(eng.hasUncaughtException(), false);
    for (int x = 0; x < 2; ++x) {
        if (x == 1)
            eng.evaluate("gc()");
        QScriptValue l = head;
        // Make sure all the nodes are still alive.
        for (int i = 0; i < 200; ++i) {
            QCOMPARE(l.property("data").toString(), QString::number(i));
            l = l.property("next");
        }
    }
}

class EventReceiver : public QObject
{
public:
    EventReceiver() {
        received = false;
    }

    bool event(QEvent *e) {
        received |= (e->type() == QEvent::User + 1);
        return QObject::event(e);
    }

    bool received;
};

void tst_QScriptEngine::processEventsWhileRunning()
{
    for (int x = 0; x < 2; ++x) {
        QScriptEngine eng;
        if (x == 0)
            eng.pushContext();

        // This is running for a silly amount of time just to make sure
        // the script doesn't finish before event processing is triggered.
        QString script = QString::fromLatin1(
            "var end = Number(new Date()) + 2000;"
            "var x = 0;"
            "while (Number(new Date()) < end) {"
            "    ++x;"
            "}");

        EventReceiver receiver;
        QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));

        eng.evaluate(script);
        QVERIFY(!eng.hasUncaughtException());
        QVERIFY(!receiver.received);

        QCOMPARE(eng.processEventsInterval(), -1);
        eng.setProcessEventsInterval(100);
        eng.evaluate(script);
        QVERIFY(!eng.hasUncaughtException());
        QVERIFY(receiver.received);

        if (x == 0)
            eng.popContext();
    }
}

class EventReceiver2 : public QObject
{
public:
    EventReceiver2(QScriptEngine *eng) {
        engine = eng;
    }

    bool event(QEvent *e) {
        if (e->type() == QEvent::User + 1) {
            engine->currentContext()->throwError("Killed");
        }
        return QObject::event(e);
    }

    QScriptEngine *engine;
};

void tst_QScriptEngine::throwErrorFromProcessEvents_data()
{
    QTest::addColumn<QString>("script");
    QTest::addColumn<QString>("error");

    QTest::newRow("while (1)")
        << QString::fromLatin1("while (1) { }")
        << QString::fromLatin1("Error: Killed");
    QTest::newRow("while (1) i++")
        << QString::fromLatin1("i = 0; while (1) { i++; }")
        << QString::fromLatin1("Error: Killed");
    // Unlike abortEvaluation(), scripts should be able to catch the
    // exception.
    QTest::newRow("try catch")
        << QString::fromLatin1("try {"
                               "    while (1) { }"
                               "} catch(e) {"
                               "    throw new Error('Caught');"
                               "}")
        << QString::fromLatin1("Error: Caught");
}

void tst_QScriptEngine::throwErrorFromProcessEvents()
{
    QFETCH(QString, script);
    QFETCH(QString, error);

    QScriptEngine eng;

    EventReceiver2 receiver(&eng);
    QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));

    eng.setProcessEventsInterval(100);
    QScriptValue ret = eng.evaluate(script);
    QVERIFY(ret.isError());
    QCOMPARE(ret.toString(), error);
}

void tst_QScriptEngine::disableProcessEventsInterval()
{
    QScriptEngine eng;
    eng.setProcessEventsInterval(100);
    QCOMPARE(eng.processEventsInterval(), 100);
    eng.setProcessEventsInterval(0);
    QCOMPARE(eng.processEventsInterval(), 0);
    eng.setProcessEventsInterval(-1);
    QCOMPARE(eng.processEventsInterval(), -1);
    eng.setProcessEventsInterval(-100);
    QCOMPARE(eng.processEventsInterval(), -100);
}

void tst_QScriptEngine::stacktrace()
{
    QString script = QString::fromLatin1(
        "function foo(counter) {\n"
        "    switch (counter) {\n"
        "        case 0: foo(counter+1); break;\n"
        "        case 1: foo(counter+1); break;\n"
        "        case 2: foo(counter+1); break;\n"
        "        case 3: foo(counter+1); break;\n"
        "        case 4: foo(counter+1); break;\n"
        "        default:\n"
        "        throw new Error('blah');\n"
        "    }\n"
        "}\n"
        "foo(0);");

    const QString fileName("testfile");

    QStringList backtrace;
    backtrace << "foo(counter = 5) at testfile:9"
              << "foo(counter = 4) at testfile:7"
              << "foo(counter = 3) at testfile:6"
              << "foo(counter = 2) at testfile:5"
              << "foo(counter = 1) at testfile:4"
              << "foo(counter = 0) at testfile:3"
              << "<global>() at testfile:12";

    QScriptEngine eng;
    QScriptValue result = eng.evaluate(script, fileName);
    QVERIFY(eng.hasUncaughtException());
    QVERIFY(result.isError());

    QCOMPARE(eng.uncaughtExceptionBacktrace(), backtrace);
    QVERIFY(eng.hasUncaughtException());
    QVERIFY(result.strictlyEquals(eng.uncaughtException()));

    QCOMPARE(result.property("fileName").toString(), fileName);
    QCOMPARE(result.property("lineNumber").toInt32(), 9);

    // throw something that isn't an Error object
    eng.clearExceptions();
    QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
    QString script2 = QString::fromLatin1(
        "function foo(counter) {\n"
        "    switch (counter) {\n"
        "        case 0: foo(counter+1); break;\n"
        "        case 1: foo(counter+1); break;\n"
        "        case 2: foo(counter+1); break;\n"
        "        case 3: foo(counter+1); break;\n"
        "        case 4: foo(counter+1); break;\n"
        "        default:\n"
        "        throw 'just a string';\n"
        "    }\n"
        "}\n"
        "foo(0);");

    QScriptValue result2 = eng.evaluate(script2, fileName);
    QVERIFY(eng.hasUncaughtException());
    QVERIFY(!result2.isError());
    QVERIFY(result2.isString());

    QCOMPARE(eng.uncaughtExceptionBacktrace(), backtrace);
    QVERIFY(eng.hasUncaughtException());

    eng.clearExceptions();
    QVERIFY(!eng.hasUncaughtException());
    QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
}

void tst_QScriptEngine::stacktrace_callJSFromCpp_data()
{
    QTest::addColumn<QString>("callbackExpression");

    QTest::newRow("explicit throw") << QString::fromLatin1("throw new Error('callback threw')");
    QTest::newRow("reference error") << QString::fromLatin1("noSuchFunction()");
}

// QTBUG-26889
void tst_QScriptEngine::stacktrace_callJSFromCpp()
{
    struct CallbackCaller {
        static QScriptValue call(QScriptContext *, QScriptEngine *eng)
        { return eng->globalObject().property(QStringLiteral("callback")).call(); }

    };

    QFETCH(QString, callbackExpression);
    QString script = QString::fromLatin1(
                "function callback() {\n"
                "    %0\n"
                "}\n"
                "callCallbackFromCpp()").arg(callbackExpression);

    QScriptEngine eng;
    eng.globalObject().setProperty(QStringLiteral("callCallbackFromCpp"),
                                   eng.newFunction(&CallbackCaller::call));
    eng.evaluate(script, QStringLiteral("test.js"));

    QVERIFY(eng.hasUncaughtException());
    QCOMPARE(eng.uncaughtExceptionLineNumber(), 2);

    QStringList expectedBacktrace;
    expectedBacktrace << QStringLiteral("callback() at test.js:2")
                      << QStringLiteral("<native>() at -1")
                      << QStringLiteral("<global>() at test.js:4");
    QCOMPARE(eng.uncaughtExceptionBacktrace(), expectedBacktrace);
}

void tst_QScriptEngine::numberParsing_data()
{
    QTest::addColumn<QString>("string");
    QTest::addColumn<qsreal>("expect");

    QTest::newRow("decimal 0") << QString("0") << qsreal(0);
    QTest::newRow("octal 0") << QString("00") << qsreal(00);
    QTest::newRow("hex 0") << QString("0x0") << qsreal(0x0);
    QTest::newRow("decimal 100") << QString("100") << qsreal(100);
    QTest::newRow("hex 100") << QString("0x100") << qsreal(0x100);
    QTest::newRow("octal 100") << QString("0100") << qsreal(0100);
    QTest::newRow("decimal 4G") << QString("4294967296") << qsreal(Q_UINT64_C(4294967296));
    QTest::newRow("hex 4G") << QString("0x100000000") << qsreal(Q_UINT64_C(0x100000000));
    QTest::newRow("octal 4G") << QString("040000000000") << qsreal(Q_UINT64_C(040000000000));
    QTest::newRow("0.5") << QString("0.5") << qsreal(0.5);
    QTest::newRow("1.5") << QString("1.5") << qsreal(1.5);
    QTest::newRow("1e2") << QString("1e2") << qsreal(100);
}

void tst_QScriptEngine::numberParsing()
{
    QFETCH(QString, string);
    QFETCH(qsreal, expect);

    QScriptEngine eng;
    QScriptValue ret = eng.evaluate(string);
    QVERIFY(ret.isNumber());
    qsreal actual = ret.toNumber();
    QCOMPARE(actual, expect);
}

// see ECMA-262, section 7.9
// This is testing ECMA compliance, not our C++ API, but it's important that
// the back-end is conformant in this regard.
void tst_QScriptEngine::automaticSemicolonInsertion()
{
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("{ 1 2 } 3");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains("SyntaxError"));
    }
    {
        QScriptValue ret = eng.evaluate("{ 1\n2 } 3");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 3);
    }
    {
        QScriptValue ret = eng.evaluate("for (a; b\n)");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains("SyntaxError"));
    }
    {
        QScriptValue ret = eng.evaluate("(function() { return\n1 + 2 })()");
        QVERIFY(ret.isUndefined());
    }
    {
        eng.evaluate("c = 2; b = 1");
        QScriptValue ret = eng.evaluate("a = b\n++c");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 3);
    }
    {
        QScriptValue ret = eng.evaluate("if (a > b)\nelse c = d");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains("SyntaxError"));
    }
    {
        eng.evaluate("function c() { return { foo: function() { return 5; } } }");
        eng.evaluate("b = 1; d = 2; e = 3");
        QScriptValue ret = eng.evaluate("a = b + c\n(d + e).foo()");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 6);
    }
    {
        QScriptValue ret = eng.evaluate("throw\n1");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains("SyntaxError"));
    }
    {
        QScriptValue ret = eng.evaluate("a = Number(1)\n++a");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 2);
    }

    // "a semicolon is never inserted automatically if the semicolon
    // would then be parsed as an empty statement"
    {
        eng.evaluate("a = 123");
        QScriptValue ret = eng.evaluate("if (0)\n ++a; a");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
    }
    {
        eng.evaluate("a = 123");
        QScriptValue ret = eng.evaluate("if (0)\n --a; a");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
    }
    {
        eng.evaluate("a = 123");
        QScriptValue ret = eng.evaluate("if ((0))\n ++a; a");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
    }
    {
        eng.evaluate("a = 123");
        QScriptValue ret = eng.evaluate("if ((0))\n --a; a");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
    }
    {
        eng.evaluate("a = 123");
        QScriptValue ret = eng.evaluate("if (0\n)\n ++a; a");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
    }
    {
        eng.evaluate("a = 123");
        QScriptValue ret = eng.evaluate("if (0\n ++a; a");
        QVERIFY(ret.isError());
    }
    {
        eng.evaluate("a = 123");
        QScriptValue ret = eng.evaluate("if (0))\n ++a; a");
        QVERIFY(ret.isError());
    }
    {
        QScriptValue ret = eng.evaluate("n = 0; for (i = 0; i < 10; ++i)\n ++n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 10);
    }
    {
        QScriptValue ret = eng.evaluate("n = 30; for (i = 0; i < 10; ++i)\n --n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 20);
    }
    {
        QScriptValue ret = eng.evaluate("n = 0; for (var i = 0; i < 10; ++i)\n ++n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 10);
    }
    {
        QScriptValue ret = eng.evaluate("n = 30; for (var i = 0; i < 10; ++i)\n --n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 20);
    }
    {
        QScriptValue ret = eng.evaluate("n = 0; i = 0; while (i++ < 10)\n ++n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 10);
    }
    {
        QScriptValue ret = eng.evaluate("n = 30; i = 0; while (i++ < 10)\n --n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 20);
    }
    {
        QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (i in o)\n ++n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 3);
    }
    {
        QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (i in o)\n --n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 6);
    }
    {
        QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (var i in o)\n ++n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 3);
    }
    {
        QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (var i in o)\n --n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 6);
    }
    {
        QScriptValue ret = eng.evaluate("o = { n: 3 }; n = 5; with (o)\n ++n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 5);
    }
    {
        QScriptValue ret = eng.evaluate("o = { n: 3 }; n = 10; with (o)\n --n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 10);
    }
    {
        QScriptValue ret = eng.evaluate("n = 5; i = 0; do\n ++n; while (++i < 10); n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 15);
    }
    {
        QScriptValue ret = eng.evaluate("n = 20; i = 0; do\n --n; while (++i < 10); n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 10);
    }

    {
        QScriptValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n++n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 2);
    }
    {
        QScriptValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n--n; n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 0);
    }

    {
        QScriptValue ret = eng.evaluate("if (0)");
        QVERIFY(ret.isError());
    }
    {
        QScriptValue ret = eng.evaluate("while (0)");
        QVERIFY(ret.isError());
    }
    {
        QScriptValue ret = eng.evaluate("for (;;)");
        QVERIFY(ret.isError());
    }
    {
        QScriptValue ret = eng.evaluate("for (p in this)");
        QVERIFY(ret.isError());
    }
    {
        QScriptValue ret = eng.evaluate("with (this)");
        QVERIFY(ret.isError());
    }
    {
        QScriptValue ret = eng.evaluate("do");
        QVERIFY(ret.isError());
    }
}

class EventReceiver3 : public QObject
{
public:
    enum AbortionResult {
        None = 0,
        String = 1,
        Error = 2,
        Number = 3
    };

    EventReceiver3(QScriptEngine *eng) {
        engine = eng;
        resultType = None;
    }

    bool event(QEvent *e) {
        if (e->type() == QEvent::User + 1) {
            switch (resultType) {
            case None:
                engine->abortEvaluation();
                break;
            case String:
                engine->abortEvaluation(QScriptValue(engine, QString::fromLatin1("Aborted")));
                break;
            case Error:
                engine->abortEvaluation(engine->currentContext()->throwError("AbortedWithError"));
                break;
            case Number:
                engine->abortEvaluation(QScriptValue(1234));
            }
        }
        return QObject::event(e);
    }

    AbortionResult resultType;
    QScriptEngine *engine;
};

static QScriptValue myFunctionAbortingEvaluation(QScriptContext *, QScriptEngine *eng)
{
    eng->abortEvaluation();
    return eng->nullValue(); // should be ignored
}

void tst_QScriptEngine::abortEvaluation_notEvaluating()
{
    QScriptEngine eng;

    eng.abortEvaluation();
    QVERIFY(!eng.hasUncaughtException());

    eng.abortEvaluation(123);
    {
        QScriptValue ret = eng.evaluate("'ciao'");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("ciao"));
    }
}

void tst_QScriptEngine::abortEvaluation_data()
{
    QTest::addColumn<QString>("script");

    QTest::newRow("while (1)")
        << QString::fromLatin1("while (1) { }");
    QTest::newRow("while (1) i++")
        << QString::fromLatin1("i = 0; while (1) { i++; }");
    QTest::newRow("try catch")
        << QString::fromLatin1("try {"
                               "    while (1) { }"
                               "} catch(e) {"
                               "    throw new Error('Caught');"
                               "}");
}

void tst_QScriptEngine::abortEvaluation()
{
    QFETCH(QString, script);

    QScriptEngine eng;
    EventReceiver3 receiver(&eng);

    eng.setProcessEventsInterval(100);
    for (int x = 0; x < 4; ++x) {
        QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
        receiver.resultType = EventReceiver3::AbortionResult(x);
        QScriptValue ret = eng.evaluate(script);
        switch (receiver.resultType) {
        case EventReceiver3::None:
            QVERIFY(!eng.hasUncaughtException());
            QVERIFY(!ret.isValid());
            break;
        case EventReceiver3::Number:
            QVERIFY(!eng.hasUncaughtException());
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 1234);
            break;
        case EventReceiver3::String:
            QVERIFY(!eng.hasUncaughtException());
            QVERIFY(ret.isString());
            QCOMPARE(ret.toString(), QString::fromLatin1("Aborted"));
            break;
        case EventReceiver3::Error:
            QVERIFY(eng.hasUncaughtException());
            QVERIFY(ret.isError());
            QCOMPARE(ret.toString(), QString::fromLatin1("Error: AbortedWithError"));
            break;
        }
    }

}

void tst_QScriptEngine::abortEvaluation_tryCatch()
{
    QScriptEngine eng;
    EventReceiver3 receiver(&eng);
    eng.setProcessEventsInterval(100);
    // scripts cannot intercept the abortion with try/catch
    for (int y = 0; y < 4; ++y) {
        QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
        receiver.resultType = EventReceiver3::AbortionResult(y);
        QScriptValue ret = eng.evaluate(QString::fromLatin1(
                                            "while (1) {\n"
                                            "  try {\n"
                                            "    (function() { while (1) { } })();\n"
                                            "  } catch (e) {\n"
                                            "    ;\n"
                                            "  }\n"
                                            "}"));
        switch (receiver.resultType) {
        case EventReceiver3::None:
            QVERIFY(!eng.hasUncaughtException());
            QVERIFY(!ret.isValid());
            break;
        case EventReceiver3::Number:
            QVERIFY(!eng.hasUncaughtException());
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 1234);
            break;
        case EventReceiver3::String:
            QVERIFY(!eng.hasUncaughtException());
            QVERIFY(ret.isString());
            QCOMPARE(ret.toString(), QString::fromLatin1("Aborted"));
            break;
        case EventReceiver3::Error:
            QVERIFY(eng.hasUncaughtException());
            QVERIFY(ret.isError());
            break;
        }
    }
}

void tst_QScriptEngine::abortEvaluation_fromNative()
{
    QScriptEngine eng;
    QScriptValue fun = eng.newFunction(myFunctionAbortingEvaluation);
    eng.globalObject().setProperty("myFunctionAbortingEvaluation", fun);
    QScriptValue ret = eng.evaluate("myFunctionAbortingEvaluation()");
    QVERIFY(!ret.isValid());
}

class ThreadedEngine : public QThread {
    Q_OBJECT

private:
    QScriptEngine* m_engine;
protected:
    void run() {
        m_engine = new QScriptEngine();
        m_engine->setGlobalObject(m_engine->newQObject(this));
        m_engine->evaluate("while(1) { sleep(); }");
        delete m_engine;
    }

public slots:
    void sleep()
    {
        QTest::qSleep(25);
        m_engine->abortEvaluation();
    }
};

void tst_QScriptEngine::abortEvaluation_QTBUG9433()
{
    ThreadedEngine engine;
    engine.start();
    QVERIFY(engine.isRunning());
    QTest::qSleep(50);
    for (uint i = 0; i < 50; ++i) { // up to ~2500 ms
        if (engine.isFinished())
            return;
        QTest::qSleep(50);
    }
    if (!engine.isFinished()) {
        engine.terminate();
        engine.wait(7000);
        QFAIL("abortEvaluation doesn't work");
    }

}

static QScriptValue myFunctionReturningIsEvaluating(QScriptContext *, QScriptEngine *eng)
{
    return QScriptValue(eng, eng->isEvaluating());
}

class EventReceiver4 : public QObject
{
public:
    EventReceiver4(QScriptEngine *eng) {
        engine = eng;
        wasEvaluating = false;
    }

    bool event(QEvent *e) {
        if (e->type() == QEvent::User + 1) {
            wasEvaluating = engine->isEvaluating();
        }
        return QObject::event(e);
    }

    QScriptEngine *engine;
    bool wasEvaluating;
};

void tst_QScriptEngine::isEvaluating_notEvaluating()
{
    QScriptEngine eng;

    QVERIFY(!eng.isEvaluating());

    eng.evaluate("");
    QVERIFY(!eng.isEvaluating());
    eng.evaluate("123");
    QVERIFY(!eng.isEvaluating());
    eng.evaluate("0 = 0");
    QVERIFY(!eng.isEvaluating());
}

void tst_QScriptEngine::isEvaluating_fromNative()
{
    QScriptEngine eng;
    QScriptValue fun = eng.newFunction(myFunctionReturningIsEvaluating);
    eng.globalObject().setProperty("myFunctionReturningIsEvaluating", fun);
    QScriptValue ret = eng.evaluate("myFunctionReturningIsEvaluating()");
    QVERIFY(ret.isBoolean());
    QVERIFY(ret.toBoolean());
}

void tst_QScriptEngine::isEvaluating_fromEvent()
{
    QScriptEngine eng;
    EventReceiver4 receiver(&eng);
    QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));

    QString script = QString::fromLatin1(
        "var end = Number(new Date()) + 1000;"
        "var x = 0;"
        "while (Number(new Date()) < end) {"
        "    ++x;"
        "}");

    eng.setProcessEventsInterval(100);
    eng.evaluate(script);
    QVERIFY(receiver.wasEvaluating);
}

static QtMsgType theMessageType;
static QString theMessage;

static void myMsgHandler(QtMsgType type, const QMessageLogContext &, const QString &msg)
{
    theMessageType = type;
    theMessage = msg;
}

void tst_QScriptEngine::printFunctionWithCustomHandler()
{
    // The built-in print() function passes the output to Qt's message
    // handler. By installing a custom message handler, the output can be
    // redirected without changing the print() function itself.
    // This behavior is not documented.
    QScriptEngine eng;
    QtMessageHandler oldHandler = qInstallMessageHandler(myMsgHandler);
    QVERIFY(eng.globalObject().property("print").isFunction());

    theMessageType = QtSystemMsg;
    QVERIFY(theMessage.isEmpty());
    QVERIFY(eng.evaluate("print('test')").isUndefined());
    QCOMPARE(theMessageType, QtDebugMsg);
    QCOMPARE(theMessage, QString::fromLatin1("test"));

    theMessageType = QtSystemMsg;
    theMessage.clear();
    QVERIFY(eng.evaluate("print(3, true, 'little pigs')").isUndefined());
    QCOMPARE(theMessageType, QtDebugMsg);
    QCOMPARE(theMessage, QString::fromLatin1("3 true little pigs"));

    qInstallMessageHandler(oldHandler);
}

void tst_QScriptEngine::printThrowsException()
{
    // If an argument to print() causes an exception to be thrown when
    // it's converted to a string, print() should propagate the exception.
    QScriptEngine eng;
    QScriptValue ret = eng.evaluate("print({ toString: function() { throw 'foo'; } });");
    QVERIFY(eng.hasUncaughtException());
    QVERIFY(ret.strictlyEquals(QScriptValue(&eng, QLatin1String("foo"))));
}

void tst_QScriptEngine::errorConstructors()
{
    QScriptEngine eng;
    QStringList prefixes;
    prefixes << "" << "Eval" << "Range" << "Reference" << "Syntax" << "Type" << "URI";
    for (int x = 0; x < 3; ++x) {
        for (int i = 0; i < prefixes.size(); ++i) {
            QString name = prefixes.at(i) + QLatin1String("Error");
            QString code = QString(i+1, QLatin1Char('\n'));
            if (x == 0)
                code += QLatin1String("throw ");
            else if (x == 1)
                code += QLatin1String("new ");
            code += name + QLatin1String("()");
            QScriptValue ret = eng.evaluate(code);
            QVERIFY(ret.isError());
            QCOMPARE(eng.hasUncaughtException(), x == 0);
            eng.clearExceptions();
            QVERIFY(ret.toString().startsWith(name));
            if (x != 0)
                QEXPECT_FAIL("", "QTBUG-6138: JSC doesn't assign lineNumber when errors are not thrown", Continue);
            QCOMPARE(ret.property("lineNumber").toInt32(), i+2);
        }
    }
}

void tst_QScriptEngine::argumentsProperty_globalContext()
{
    QScriptEngine eng;
    {
        // Unlike function calls, the global context doesn't have an
        // arguments property.
        QScriptValue ret = eng.evaluate("arguments");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
    }
    eng.evaluate("arguments = 10");
    {
        QScriptValue ret = eng.evaluate("arguments");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 10);
    }
    QVERIFY(eng.evaluate("delete arguments").toBoolean());
    QVERIFY(!eng.globalObject().property("arguments").isValid());
}

void tst_QScriptEngine::argumentsProperty_JS()
{
    {
        QScriptEngine eng;
        eng.evaluate("o = { arguments: 123 }");
        QScriptValue ret = eng.evaluate("with (o) { arguments; }");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
    }
    {
        QScriptEngine eng;
        QVERIFY(!eng.globalObject().property("arguments").isValid());
        // This is testing ECMA-262 compliance. In function calls, "arguments"
        // appears like a local variable, and it can be replaced.
        QScriptValue ret = eng.evaluate("(function() { arguments = 456; return arguments; })()");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 456);
        QVERIFY(!eng.globalObject().property("arguments").isValid());
    }
}

static QScriptValue argumentsProperty_fun(QScriptContext *, QScriptEngine *eng)
{
    // Since evaluate() is done in the current context, "arguments" should
    // refer to currentContext()->argumentsObject().
    // This is for consistency with the built-in JS eval() function.
    eng->evaluate("var a = arguments[0];");
    eng->evaluate("arguments[0] = 200;");
    return eng->evaluate("a + arguments[0]");
}

void tst_QScriptEngine::argumentsProperty_evaluateInNativeFunction()
{
    QScriptEngine eng;
    QScriptValue fun = eng.newFunction(argumentsProperty_fun);
    eng.globalObject().setProperty("fun", eng.newFunction(argumentsProperty_fun));
    QScriptValue result = eng.evaluate("fun(18)");
    QVERIFY(result.isNumber());
    QCOMPARE(result.toInt32(), 200+18);
}

void tst_QScriptEngine::jsNumberClass()
{
    // See ECMA-262 Section 15.7, "Number Objects".

    QScriptEngine eng;

    QScriptValue ctor = eng.globalObject().property("Number");
    QVERIFY(ctor.property("length").isNumber());
    QCOMPARE(ctor.property("length").toNumber(), qsreal(1));
    QScriptValue proto = ctor.property("prototype");
    QVERIFY(proto.isObject());
    {
        QScriptValue::PropertyFlags flags = QScriptValue::SkipInEnumeration
                                            | QScriptValue::Undeletable
                                            | QScriptValue::ReadOnly;
        QCOMPARE(ctor.propertyFlags("prototype"), flags);
        QVERIFY(ctor.property("MAX_VALUE").isNumber());
        QCOMPARE(ctor.propertyFlags("MAX_VALUE"), flags);
        QVERIFY(ctor.property("MIN_VALUE").isNumber());
        QCOMPARE(ctor.propertyFlags("MIN_VALUE"), flags);
        QVERIFY(ctor.property("NaN").isNumber());
        QCOMPARE(ctor.propertyFlags("NaN"), flags);
        QVERIFY(ctor.property("NEGATIVE_INFINITY").isNumber());
        QCOMPARE(ctor.propertyFlags("NEGATIVE_INFINITY"), flags);
        QVERIFY(ctor.property("POSITIVE_INFINITY").isNumber());
        QCOMPARE(ctor.propertyFlags("POSITIVE_INFINITY"), flags);
    }
    QVERIFY(proto.instanceOf(eng.globalObject().property("Object")));
    QCOMPARE(proto.toNumber(), qsreal(0));
    QVERIFY(proto.property("constructor").strictlyEquals(ctor));

    {
        QScriptValue ret = eng.evaluate("Number()");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toNumber(), qsreal(0));
    }
    {
        QScriptValue ret = eng.evaluate("Number(123)");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toNumber(), qsreal(123));
    }
    {
        QScriptValue ret = eng.evaluate("Number('456')");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toNumber(), qsreal(456));
    }
    {
        QScriptValue ret = eng.evaluate("new Number()");
        QVERIFY(!ret.isNumber());
        QVERIFY(ret.isObject());
        QCOMPARE(ret.toNumber(), qsreal(0));
    }
    {
        QScriptValue ret = eng.evaluate("new Number(123)");
        QVERIFY(!ret.isNumber());
        QVERIFY(ret.isObject());
        QCOMPARE(ret.toNumber(), qsreal(123));
    }
    {
        QScriptValue ret = eng.evaluate("new Number('456')");
        QVERIFY(!ret.isNumber());
        QVERIFY(ret.isObject());
        QCOMPARE(ret.toNumber(), qsreal(456));
    }

    QVERIFY(proto.property("toString").isFunction());
    {
        QScriptValue ret = eng.evaluate("new Number(123).toString()");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("123"));
    }
    {
        QScriptValue ret = eng.evaluate("new Number(123).toString(8)");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("173"));
    }
    {
        QScriptValue ret = eng.evaluate("new Number(123).toString(16)");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("7b"));
    }
    QVERIFY(proto.property("toLocaleString").isFunction());
    {
        QScriptValue ret = eng.evaluate("new Number(123).toLocaleString()");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("123"));
    }
    QVERIFY(proto.property("valueOf").isFunction());
    {
        QScriptValue ret = eng.evaluate("new Number(123).valueOf()");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toNumber(), qsreal(123));
    }
    QVERIFY(proto.property("toExponential").isFunction());
    {
        QScriptValue ret = eng.evaluate("new Number(123).toExponential()");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("1.23e+2"));
    }
    QVERIFY(proto.property("toFixed").isFunction());
    {
        QScriptValue ret = eng.evaluate("new Number(123).toFixed()");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("123"));
    }
    QVERIFY(proto.property("toPrecision").isFunction());
    {
        QScriptValue ret = eng.evaluate("new Number(123).toPrecision()");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("123"));
    }
}

void tst_QScriptEngine::jsForInStatement_simple()
{
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("o = { }; r = []; for (var p in o) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QVERIFY(lst.isEmpty());
    }
    {
        QScriptValue ret = eng.evaluate("o = { p: 123 }; r = [];"
                                        "for (var p in o) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 1);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
    }
    {
        QScriptValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];"
                                        "for (var p in o) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 2);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
        QCOMPARE(lst.at(1), QString::fromLatin1("q"));
    }
}

void tst_QScriptEngine::jsForInStatement_prototypeProperties()
{
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("o = { }; o.__proto__ = { p: 123 }; r = [];"
                                        "for (var p in o) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 1);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
    }
    {
        QScriptValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { q: 456 }; r = [];"
                                        "for (var p in o) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 2);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
        QCOMPARE(lst.at(1), QString::fromLatin1("q"));
    }
    {
        // shadowed property
        QScriptValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { p: 456 }; r = [];"
                                        "for (var p in o) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 1);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
    }

}

void tst_QScriptEngine::jsForInStatement_mutateWhileIterating()
{
    QScriptEngine eng;
    // deleting property during enumeration
    {
        QScriptValue ret = eng.evaluate("o = { p: 123 }; r = [];"
                                        "for (var p in o) { r[r.length] = p; delete r[p]; } r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 1);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
    }
    {
        QScriptValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];"
                                        "for (var p in o) { r[r.length] = p; delete o.q; } r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 1);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
    }

    // adding property during enumeration
    {
        QScriptValue ret = eng.evaluate("o = { p: 123 }; r = [];"
                                        "for (var p in o) { r[r.length] = p; o.q = 456; } r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 1);
        QCOMPARE(lst.at(0), QString::fromLatin1("p"));
    }

}

void tst_QScriptEngine::jsForInStatement_arrays()
{
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("a = [123, 456]; r = [];"
                                        "for (var p in a) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 2);
        QCOMPARE(lst.at(0), QString::fromLatin1("0"));
        QCOMPARE(lst.at(1), QString::fromLatin1("1"));
    }
    {
        QScriptValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar'; r = [];"
                                        "for (var p in a) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 3);
        QCOMPARE(lst.at(0), QString::fromLatin1("0"));
        QCOMPARE(lst.at(1), QString::fromLatin1("1"));
        QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
    }
    {
        QScriptValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar';"
                                        "b = [111, 222, 333]; b.bar = 'baz';"
                                        "a.__proto__ = b; r = [];"
                                        "for (var p in a) r[r.length] = p; r");
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), 5);
        QCOMPARE(lst.at(0), QString::fromLatin1("0"));
        QCOMPARE(lst.at(1), QString::fromLatin1("1"));
        QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
        QCOMPARE(lst.at(3), QString::fromLatin1("2"));
        QCOMPARE(lst.at(4), QString::fromLatin1("bar"));
    }
}

void tst_QScriptEngine::jsForInStatement_nullAndUndefined()
{
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("r = true; for (var p in undefined) r = false; r");
        QVERIFY(ret.isBool());
        QVERIFY(ret.toBool());
    }
    {
        QScriptValue ret = eng.evaluate("r = true; for (var p in null) r = false; r");
        QVERIFY(ret.isBool());
        QVERIFY(ret.toBool());
    }
}

void tst_QScriptEngine::jsFunctionDeclarationAsStatement()
{
    // ECMA-262 does not allow function declarations to be used as statements,
    // but several popular implementations (including JSC) do. See the NOTE
    // at the beginning of chapter 12 in ECMA-262 5th edition, where it's
    // recommended that implementations either disallow this usage or issue
    // a warning.
    // Since we had a bug report long ago about Qt Script not supporting this
    // "feature" (and thus deviating from other implementations), we still
    // check this behavior.

    QScriptEngine eng;
    QVERIFY(!eng.globalObject().property("bar").isValid());
    eng.evaluate("function foo(arg) {\n"
                 "  if (arg == 'bar')\n"
                 "    function bar() { return 'bar'; }\n"
                 "  else\n"
                 "    function baz() { return 'baz'; }\n"
                 "  return (arg == 'bar') ? bar : baz;\n"
                 "}");
    QVERIFY(!eng.globalObject().property("bar").isValid());
    QVERIFY(!eng.globalObject().property("baz").isValid());
    QVERIFY(eng.evaluate("foo").isFunction());
    {
        QScriptValue ret = eng.evaluate("foo('bar')");
        QVERIFY(ret.isFunction());
        QScriptValue ret2 = ret.call(QScriptValue());
        QCOMPARE(ret2.toString(), QString::fromLatin1("bar"));
        QVERIFY(!eng.globalObject().property("bar").isValid());
        QVERIFY(!eng.globalObject().property("baz").isValid());
    }
    {
        QScriptValue ret = eng.evaluate("foo('baz')");
        QVERIFY(ret.isFunction());
        QScriptValue ret2 = ret.call(QScriptValue());
        QCOMPARE(ret2.toString(), QString::fromLatin1("baz"));
        QVERIFY(!eng.globalObject().property("bar").isValid());
        QVERIFY(!eng.globalObject().property("baz").isValid());
    }
}

void tst_QScriptEngine::stringObjects()
{
    // See ECMA-262 Section 15.5, "String Objects".

    QScriptEngine eng;
    QString str("ciao");
    // in C++
    {
        QScriptValue obj = QScriptValue(&eng, str).toObject();
        QCOMPARE(obj.property("length").toInt32(), str.length());
        QCOMPARE(obj.propertyFlags("length"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration | QScriptValue::ReadOnly));
        for (int i = 0; i < str.length(); ++i) {
            QString pname = QString::number(i);
            QVERIFY(obj.property(pname).isString());
            QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
            QCOMPARE(obj.propertyFlags(pname), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::ReadOnly));
            obj.setProperty(pname, QScriptValue());
            obj.setProperty(pname, QScriptValue(&eng, 123));
            QVERIFY(obj.property(pname).isString());
            QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
        }
        QVERIFY(!obj.property("-1").isValid());
        QVERIFY(!obj.property(QString::number(str.length())).isValid());

        QScriptValue val(&eng, 123);
        obj.setProperty("-1", val);
        QVERIFY(obj.property("-1").strictlyEquals(val));
        obj.setProperty("100", val);
        QVERIFY(obj.property("100").strictlyEquals(val));
    }

    // in script
    {
        QScriptValue ret = eng.evaluate("s = new String('ciao'); r = []; for (var p in s) r.push(p); r");
        QVERIFY(ret.isArray());
        QStringList lst = qscriptvalue_cast<QStringList>(ret);
        QCOMPARE(lst.size(), str.length());
        for (int i = 0; i < str.length(); ++i)
            QCOMPARE(lst.at(i), QString::number(i));

        QScriptValue ret2 = eng.evaluate("s[0] = 123; s[0]");
        QVERIFY(ret2.isString());
        QCOMPARE(ret2.toString().length(), 1);
        QCOMPARE(ret2.toString().at(0), str.at(0));

        QScriptValue ret3 = eng.evaluate("s[-1] = 123; s[-1]");
        QVERIFY(ret3.isNumber());
        QCOMPARE(ret3.toInt32(), 123);

        QScriptValue ret4 = eng.evaluate("s[s.length] = 456; s[s.length]");
        QVERIFY(ret4.isNumber());
        QCOMPARE(ret4.toInt32(), 456);

        QScriptValue ret5 = eng.evaluate("delete s[0]");
        QVERIFY(ret5.isBoolean());
        QVERIFY(!ret5.toBoolean());

        QScriptValue ret6 = eng.evaluate("delete s[-1]");
        QVERIFY(ret6.isBoolean());
        QVERIFY(ret6.toBoolean());

        QScriptValue ret7 = eng.evaluate("delete s[s.length]");
        QVERIFY(ret7.isBoolean());
        QVERIFY(ret7.toBoolean());
    }
}

void tst_QScriptEngine::jsStringPrototypeReplaceBugs()
{
    QScriptEngine eng;
    // task 212440
    {
        QScriptValue ret = eng.evaluate("replace_args = []; \"a a a\".replace(/(a)/g, function() { replace_args.push(arguments); }); replace_args");
        QVERIFY(ret.isArray());
        int len = ret.property("length").toInt32();
        QCOMPARE(len, 3);
        for (int i = 0; i < len; ++i) {
            QScriptValue args = ret.property(i);
            QCOMPARE(args.property("length").toInt32(), 4);
            QCOMPARE(args.property(0).toString(), QString::fromLatin1("a")); // matched string
            QCOMPARE(args.property(1).toString(), QString::fromLatin1("a")); // capture
            QVERIFY(args.property(2).isNumber());
            QCOMPARE(args.property(2).toInt32(), i*2); // index of match
            QCOMPARE(args.property(3).toString(), QString::fromLatin1("a a a"));
        }
    }
    // task 212501
    {
        QScriptValue ret = eng.evaluate("\"foo\".replace(/a/g, function() {})");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
    }
}

void tst_QScriptEngine::getterSetterThisObject_global()
{
    {
        QScriptEngine eng;
        // read
        eng.evaluate("__defineGetter__('x', function() { return this; });");
        {
            QScriptValue ret = eng.evaluate("x");
            QVERIFY(ret.equals(eng.globalObject()));
        }
        {
            QScriptValue ret = eng.evaluate("(function() { return x; })()");
            QVERIFY(ret.equals(eng.globalObject()));
        }
        {
            QScriptValue ret = eng.evaluate("with (this) x");
            QVERIFY(ret.equals(eng.globalObject()));
        }
        {
            QScriptValue ret = eng.evaluate("with ({}) x");
            QVERIFY(ret.equals(eng.globalObject()));
        }
        {
            QScriptValue ret = eng.evaluate("(function() { with ({}) return x; })()");
            QVERIFY(ret.equals(eng.globalObject()));
        }
        // write
        eng.evaluate("__defineSetter__('x', function() { return this; });");
        {
            QScriptValue ret = eng.evaluate("x = 'foo'");
            // SpiderMonkey says setter return value, JSC says RHS.
            QVERIFY(ret.isString());
            QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
        }
        {
            QScriptValue ret = eng.evaluate("(function() { return x = 'foo'; })()");
            // SpiderMonkey says setter return value, JSC says RHS.
            QVERIFY(ret.isString());
            QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
        }
        {
            QScriptValue ret = eng.evaluate("with (this) x = 'foo'");
            // SpiderMonkey says setter return value, JSC says RHS.
            QVERIFY(ret.isString());
            QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
        }
        {
            QScriptValue ret = eng.evaluate("with ({}) x = 'foo'");
            // SpiderMonkey says setter return value, JSC says RHS.
            QVERIFY(ret.isString());
            QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
        }
        {
            QScriptValue ret = eng.evaluate("(function() { with ({}) return x = 'foo'; })()");
            // SpiderMonkey says setter return value, JSC says RHS.
            QVERIFY(ret.isString());
            QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
        }
    }
}

void tst_QScriptEngine::getterSetterThisObject_plain()
{
    {
        QScriptEngine eng;
        eng.evaluate("o = {}");
        // read
        eng.evaluate("o.__defineGetter__('x', function() { return this; })");
        QVERIFY(eng.evaluate("o.x === o").toBoolean());
        QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
        QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBoolean());
        eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o"));
        // write
        eng.evaluate("o.__defineSetter__('x', function() { return this; });");
        // SpiderMonkey says setter return value, JSC says RHS.
        QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBoolean());
        QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
        QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
    }
}

void tst_QScriptEngine::getterSetterThisObject_prototypeChain()
{
    {
        QScriptEngine eng;
        eng.evaluate("o = {}; p = {}; o.__proto__ = p");
        // read
        eng.evaluate("p.__defineGetter__('x', function() { return this; })");
        QVERIFY(eng.evaluate("o.x === o").toBoolean());
        QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
        QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBoolean());
        eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o"));
        eng.evaluate("with (q) with (o) x").equals(eng.evaluate("o"));
        // write
        eng.evaluate("o.__defineSetter__('x', function() { return this; });");
        // SpiderMonkey says setter return value, JSC says RHS.
        QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBoolean());
        QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
        QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
    }
}

void tst_QScriptEngine::getterSetterThisObject_activation()
{
    {
        QScriptEngine eng;
        QScriptContext *ctx = eng.pushContext();
        QVERIFY(ctx != 0);
        QScriptValue act = ctx->activationObject();
        act.setProperty("act", act);
        // read
        eng.evaluate("act.__defineGetter__('x', function() { return this; })");
        QVERIFY(eng.evaluate("x === act").toBoolean());
        QEXPECT_FAIL("", "QTBUG-17605: Not possible to implement local variables as getter/setter properties", Abort);
        QVERIFY(!eng.hasUncaughtException());
        QVERIFY(eng.evaluate("with (act) x").equals("foo"));
        QVERIFY(eng.evaluate("(function() { with (act) return x; })() === act").toBoolean());
        eng.evaluate("q = {}; with (act) with (q) x").equals(eng.evaluate("act"));
        eng.evaluate("with (q) with (act) x").equals(eng.evaluate("act"));
        // write
        eng.evaluate("act.__defineSetter__('x', function() { return this; });");
        QVERIFY(eng.evaluate("(x = 'foo') === 'foo'").toBoolean());
        QVERIFY(eng.evaluate("with (act) x = 'foo'").equals("foo"));
        QVERIFY(eng.evaluate("with (act) with (q) x = 'foo'").equals("foo"));
        eng.popContext();
    }
}

void tst_QScriptEngine::jsContinueInSwitch()
{
    // This is testing ECMA-262 compliance, not C++ API.

    QScriptEngine eng;
    // switch - continue
    {
        QScriptValue ret = eng.evaluate("switch (1) { default: continue; }");
        QVERIFY(ret.isError());
    }
    // for - switch - case - continue
    {
        QScriptValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
                                        "  switch (i) {\n"
                                        "    case 1: ++j; continue;\n"
                                        "    case 100: ++j; continue;\n"
                                        "    case 1000: ++j; continue;\n"
                                        "  }\n"
                                        "}; j");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 3);
    }
    // for - switch - case - default - continue
    {
        QScriptValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
                                        "  switch (i) {\n"
                                        "    case 1: ++j; continue;\n"
                                        "    case 100: ++j; continue;\n"
                                        "    case 1000: ++j; continue;\n"
                                        "    default: if (i < 50000) break; else continue;\n"
                                        "  }\n"
                                        "}; j");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 3);
    }
    // switch - for - continue
    {
        QScriptValue ret = eng.evaluate("j = 123; switch (j) {\n"
                                        "  case 123:\n"
                                        "  for (i = 0; i < 100000; ++i) {\n"
                                        "    continue;\n"
                                        "  }\n"
                                        "}; i\n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 100000);
    }
    // switch - switch - continue
    {
        QScriptValue ret = eng.evaluate("i = 1; switch (i) { default: switch (i) { case 1: continue; } }");
        QVERIFY(ret.isError());
    }
    // for - switch - switch - continue
    {
        QScriptValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
                                        "  switch (i) {\n"
                                        "    case 1:\n"
                                        "    switch (i) {\n"
                                        "      case 1: ++j; continue;\n"
                                        "    }\n"
                                        "  }\n"
                                        "}; j");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 1);
    }
    // switch - for - switch - continue
    {
        QScriptValue ret = eng.evaluate("j = 123; switch (j) {\n"
                                        "  case 123:\n"
                                        "  for (i = 0; i < 100000; ++i) {\n"
                                        "    switch (i) {\n"
                                        "      case 1:\n"
                                        "      ++j; continue;\n"
                                        "    }\n"
                                        "  }\n"
                                        "}; i\n");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 100000);
    }
}

void tst_QScriptEngine::jsShadowReadOnlyPrototypeProperty()
{
    // SpiderMonkey has different behavior than JSC and V8; it disallows
    // creating a property on the instance if there's a property with the
    // same name in the prototype, and that property is read-only. We
    // adopted that behavior in the old (4.5) Qt Script back-end, but it
    // just seems weird -- and non-compliant. Adopt the JSC behavior instead.
    QScriptEngine eng;
    QVERIFY(eng.evaluate("o = {}; o.__proto__ = parseInt; o.length").isNumber());
    QCOMPARE(eng.evaluate("o.length = 123; o.length").toInt32(), 123);
    QVERIFY(eng.evaluate("o.hasOwnProperty('length')").toBoolean());
}

void tst_QScriptEngine::toObject()
{
    QScriptEngine eng;

    QVERIFY(!eng.toObject(eng.undefinedValue()).isValid());

    QVERIFY(!eng.toObject(eng.nullValue()).isValid());

    QScriptValue falskt(false);
    {
        QScriptValue tmp = eng.toObject(falskt);
        QVERIFY(tmp.isObject());
        QCOMPARE(tmp.toNumber(), falskt.toNumber());
    }
    QVERIFY(falskt.isBool());

    QScriptValue sant(true);
    {
        QScriptValue tmp = eng.toObject(sant);
        QVERIFY(tmp.isObject());
        QCOMPARE(tmp.toNumber(), sant.toNumber());
    }
    QVERIFY(sant.isBool());

    QScriptValue number(123.0);
    {
        QScriptValue tmp = eng.toObject(number);
        QVERIFY(tmp.isObject());
        QCOMPARE(tmp.toNumber(), number.toNumber());
    }
    QVERIFY(number.isNumber());

    QScriptValue str = QScriptValue(&eng, QString("ciao"));
    {
        QScriptValue tmp = eng.toObject(str);
        QVERIFY(tmp.isObject());
        QCOMPARE(tmp.toString(), str.toString());
    }
    QVERIFY(str.isString());

    QScriptValue object = eng.newObject();
    {
        QScriptValue tmp = eng.toObject(object);
        QVERIFY(tmp.isObject());
        QVERIFY(tmp.strictlyEquals(object));
    }

    QScriptValue qobject = eng.newQObject(this);
    QVERIFY(eng.toObject(qobject).strictlyEquals(qobject));

    QVERIFY(!eng.toObject(QScriptValue()).isValid());

    // v1 constructors

    QScriptValue boolValue(&eng, true);
    {
        QScriptValue ret = eng.toObject(boolValue);
        QVERIFY(ret.isObject());
        QCOMPARE(ret.toBool(), boolValue.toBool());
    }
    QVERIFY(boolValue.isBool());

    QScriptValue numberValue(&eng, 123.0);
    {
        QScriptValue ret = eng.toObject(numberValue);
        QVERIFY(ret.isObject());
        QCOMPARE(ret.toNumber(), numberValue.toNumber());
    }
    QVERIFY(numberValue.isNumber());

    QScriptValue stringValue(&eng, QString::fromLatin1("foo"));
    {
        QScriptValue ret = eng.toObject(stringValue);
        QVERIFY(ret.isObject());
        QCOMPARE(ret.toString(), stringValue.toString());
    }
    QVERIFY(stringValue.isString());
}

void tst_QScriptEngine::jsReservedWords_data()
{
    QTest::addColumn<QString>("word");
    QTest::newRow("break") << QString("break");
    QTest::newRow("case") << QString("case");
    QTest::newRow("catch") << QString("catch");
    QTest::newRow("continue") << QString("continue");
    QTest::newRow("default") << QString("default");
    QTest::newRow("delete") << QString("delete");
    QTest::newRow("do") << QString("do");
    QTest::newRow("else") << QString("else");
    QTest::newRow("false") << QString("false");
    QTest::newRow("finally") << QString("finally");
    QTest::newRow("for") << QString("for");
    QTest::newRow("function") << QString("function");
    QTest::newRow("if") << QString("if");
    QTest::newRow("in") << QString("in");
    QTest::newRow("instanceof") << QString("instanceof");
    QTest::newRow("new") << QString("new");
    QTest::newRow("null") << QString("null");
    QTest::newRow("return") << QString("return");
    QTest::newRow("switch") << QString("switch");
    QTest::newRow("this") << QString("this");
    QTest::newRow("throw") << QString("throw");
    QTest::newRow("true") << QString("true");
    QTest::newRow("try") << QString("try");
    QTest::newRow("typeof") << QString("typeof");
    QTest::newRow("var") << QString("var");
    QTest::newRow("void") << QString("void");
    QTest::newRow("while") << QString("while");
    QTest::newRow("with") << QString("with");
}

void tst_QScriptEngine::jsReservedWords()
{
    // See ECMA-262 Section 7.6.1, "Reserved Words".
    // We prefer that the implementation is less strict than the spec; e.g.
    // it's good to allow reserved words as identifiers in object literals,
    // and when accessing properties using dot notation.

    QFETCH(QString, word);
    {
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate(word + " = 123");
        QVERIFY(ret.isError());
        QString str = ret.toString();
        QVERIFY(str.startsWith("SyntaxError") || str.startsWith("ReferenceError"));
    }
    {
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate("var " + word + " = 123");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().startsWith("SyntaxError"));
    }
    {
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate("o = {}; o." + word + " = 123");
        // in the old back-end and in SpiderMonkey this is allowed, but not in JSC
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().startsWith("SyntaxError"));
    }
    {
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate("o = { " + word + ": 123 }");
        // in the old back-end and in SpiderMonkey this is allowed, but not in JSC
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().startsWith("SyntaxError"));
    }
    {
        // SpiderMonkey allows this, but we don't
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate("function " + word + "() {}");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().startsWith("SyntaxError"));
    }
}

void tst_QScriptEngine::jsFutureReservedWords_data()
{
    QTest::addColumn<QString>("word");
    QTest::addColumn<bool>("allowed");
    QTest::newRow("abstract") << QString("abstract") << true;
    QTest::newRow("boolean") << QString("boolean") << true;
    QTest::newRow("byte") << QString("byte") << true;
    QTest::newRow("char") << QString("char") << true;
    QTest::newRow("class") << QString("class") << false;
    QTest::newRow("const") << QString("const") << false;
    QTest::newRow("debugger") << QString("debugger") << false;
    QTest::newRow("double") << QString("double") << true;
    QTest::newRow("enum") << QString("enum") << false;
    QTest::newRow("export") << QString("export") << false;
    QTest::newRow("extends") << QString("extends") << false;
    QTest::newRow("final") << QString("final") << true;
    QTest::newRow("float") << QString("float") << true;
    QTest::newRow("goto") << QString("goto") << true;
    QTest::newRow("implements") << QString("implements") << true;
    QTest::newRow("import") << QString("import") << false;
    QTest::newRow("int") << QString("int") << true;
    QTest::newRow("interface") << QString("interface") << true;
    QTest::newRow("long") << QString("long") << true;
    QTest::newRow("native") << QString("native") << true;
    QTest::newRow("package") << QString("package") << true;
    QTest::newRow("private") << QString("private") << true;
    QTest::newRow("protected") << QString("protected") << true;
    QTest::newRow("public") << QString("public") << true;
    QTest::newRow("short") << QString("short") << true;
    QTest::newRow("static") << QString("static") << true;
    QTest::newRow("super") << QString("super") << false;
    QTest::newRow("synchronized") << QString("synchronized") << true;
    QTest::newRow("throws") << QString("throws") << true;
    QTest::newRow("transient") << QString("transient") << true;
    QTest::newRow("volatile") << QString("volatile") << true;
}

void tst_QScriptEngine::jsFutureReservedWords()
{
    // See ECMA-262 Section 7.6.1.2, "Future Reserved Words".
    // In real-world implementations, most of these words are
    // actually allowed as normal identifiers.

    QFETCH(QString, word);
    QFETCH(bool, allowed);
    {
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate(word + " = 123");
        QCOMPARE(!ret.isError(), allowed);
    }
    {
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate("var " + word + " = 123");
        QCOMPARE(!ret.isError(), allowed);
    }
    {
        // this should probably be allowed (see task 162567)
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate("o = {}; o." + word + " = 123");
        QCOMPARE(ret.isNumber(), allowed);
        QCOMPARE(!ret.isError(), allowed);
    }
    {
        // this should probably be allowed (see task 162567)
        QScriptEngine eng;
        QScriptValue ret = eng.evaluate("o = { " + word + ": 123 }");
        QCOMPARE(!ret.isError(), allowed);
    }
}

void tst_QScriptEngine::jsThrowInsideWithStatement()
{
    // This is testing ECMA-262 compliance, not C++ API.

    // task 209988
    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate(
            "try {"
            "  o = { bad : \"bug\" };"
            "  with (o) {"
            "    throw 123;"
            "  }"
            "} catch (e) {"
            "  bad;"
            "}");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
    }
    {
        QScriptValue ret = eng.evaluate(
            "try {"
            "  o = { bad : \"bug\" };"
            "  with (o) {"
            "    throw 123;"
            "  }"
            "} finally {"
            "  bad;"
            "}");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
    }
    {
        eng.clearExceptions();
        QScriptValue ret = eng.evaluate(
            "o = { bug : \"no bug\" };"
            "with (o) {"
            "  try {"
            "    throw 123;"
            "  } finally {"
            "    bug;"
            "  }"
            "}");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 123);
        QVERIFY(eng.hasUncaughtException());
    }
    {
        eng.clearExceptions();
        QScriptValue ret = eng.evaluate(
            "o = { bug : \"no bug\" };"
            "with (o) {"
            "    throw 123;"
            "}");
        QVERIFY(ret.isNumber());
        QScriptValue ret2 = eng.evaluate("bug");
        QVERIFY(ret2.isError());
        QVERIFY(ret2.toString().contains(QString::fromLatin1("ReferenceError")));
    }
}

class TestAgent : public QScriptEngineAgent
{
public:
    TestAgent(QScriptEngine *engine) : QScriptEngineAgent(engine) {}
};

void tst_QScriptEngine::getSetAgent_ownership()
{
    // engine deleted before agent --> agent deleted too
    QScriptEngine *eng = new QScriptEngine;
    QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
    TestAgent *agent = new TestAgent(eng);
    eng->setAgent(agent);
    QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent);
    eng->setAgent(0); // the engine maintains ownership of the old agent
    QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
    delete eng;
}

void tst_QScriptEngine::getSetAgent_deleteAgent()
{
    // agent deleted before engine --> engine's agent should become 0
    QScriptEngine *eng = new QScriptEngine;
    TestAgent *agent = new TestAgent(eng);
    eng->setAgent(agent);
    QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent);
    delete agent;
    QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
    eng->evaluate("(function(){ return 123; })()");
    delete eng;
}

void tst_QScriptEngine::getSetAgent_differentEngine()
{
    QScriptEngine eng;
    QScriptEngine eng2;
    TestAgent *agent = new TestAgent(&eng);
    QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::setAgent(): cannot set agent belonging to different engine");
    eng2.setAgent(agent);
    QCOMPARE(eng2.agent(), (QScriptEngineAgent*)0);
}

void tst_QScriptEngine::reentrancy_stringHandles()
{
    QScriptEngine eng1;
    QScriptEngine eng2;
    QScriptString s1 = eng1.toStringHandle("foo");
    QScriptString s2 = eng2.toStringHandle("foo");
    QVERIFY(s1 != s2);
}

void tst_QScriptEngine::reentrancy_processEventsInterval()
{
    QScriptEngine eng1;
    QScriptEngine eng2;
    eng1.setProcessEventsInterval(123);
    QCOMPARE(eng2.processEventsInterval(), -1);
    eng2.setProcessEventsInterval(456);
    QCOMPARE(eng1.processEventsInterval(), 123);
}

void tst_QScriptEngine::reentrancy_typeConversion()
{
    QScriptEngine eng1;
    QScriptEngine eng2;
    qScriptRegisterMetaType<Foo>(&eng1, fooToScriptValue, fooFromScriptValue);
    Foo foo;
    foo.x = 12;
    foo.y = 34;
    {
        QScriptValue fooVal = qScriptValueFromValue(&eng1, foo);
        QVERIFY(fooVal.isObject());
        QVERIFY(!fooVal.isVariant());
        QCOMPARE(fooVal.property("x").toInt32(), 12);
        QCOMPARE(fooVal.property("y").toInt32(), 34);
        fooVal.setProperty("x", 56);
        fooVal.setProperty("y", 78);

        Foo foo2 = qScriptValueToValue<Foo>(fooVal);
        QCOMPARE(foo2.x, 56);
        QCOMPARE(foo2.y, 78);
    }
    {
        QScriptValue fooVal = qScriptValueFromValue(&eng2, foo);
        QVERIFY(fooVal.isVariant());

        Foo foo2 = qScriptValueToValue<Foo>(fooVal);
        QCOMPARE(foo2.x, 12);
        QCOMPARE(foo2.y, 34);
    }
    QVERIFY(!eng1.defaultPrototype(qMetaTypeId<Foo>()).isValid());
    QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
    QScriptValue proto1 = eng1.newObject();
    eng1.setDefaultPrototype(qMetaTypeId<Foo>(), proto1);
    QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
    QScriptValue proto2 = eng2.newObject();
    eng2.setDefaultPrototype(qMetaTypeId<Foo>(), proto2);
    QVERIFY(eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
    QVERIFY(eng1.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(proto1));
}

void tst_QScriptEngine::reentrancy_globalObjectProperties()
{
    QScriptEngine eng1;
    QScriptEngine eng2;
    QVERIFY(!eng2.globalObject().property("a").isValid());
    eng1.evaluate("a = 10");
    QVERIFY(eng1.globalObject().property("a").isNumber());
    QVERIFY(!eng2.globalObject().property("a").isValid());
    eng2.evaluate("a = 20");
    QVERIFY(eng2.globalObject().property("a").isNumber());
    QCOMPARE(eng1.globalObject().property("a").toInt32(), 10);
}

void tst_QScriptEngine::reentrancy_Array()
{
    // weird bug with JSC backend
    {
        QScriptEngine eng;
        QCOMPARE(eng.evaluate("Array()").toString(), QString());
        eng.evaluate("Array.prototype.toString");
        QCOMPARE(eng.evaluate("Array()").toString(), QString());
    }
    {
        QScriptEngine eng;
        QCOMPARE(eng.evaluate("Array()").toString(), QString());
    }
}

void tst_QScriptEngine::reentrancy_objectCreation()
{
    QScriptEngine eng1;
    QScriptEngine eng2;
    {
        QScriptValue d1 = eng1.newDate(0);
        QScriptValue d2 = eng2.newDate(0);
        QCOMPARE(d1.toDateTime(), d2.toDateTime());
        QCOMPARE(d2.toDateTime(), d1.toDateTime());
    }
    {
        QScriptValue r1 = eng1.newRegExp("foo", "gim");
        QScriptValue r2 = eng2.newRegExp("foo", "gim");
        QCOMPARE(r1.toRegExp(), r2.toRegExp());
        QCOMPARE(r2.toRegExp(), r1.toRegExp());
    }
    {
        QScriptValue o1 = eng1.newQObject(this);
        QScriptValue o2 = eng2.newQObject(this);
        QCOMPARE(o1.toQObject(), o2.toQObject());
        QCOMPARE(o2.toQObject(), o1.toQObject());
    }
    {
        QScriptValue mo1 = eng1.newQMetaObject(&staticMetaObject);
        QScriptValue mo2 = eng2.newQMetaObject(&staticMetaObject);
        QCOMPARE(mo1.toQMetaObject(), mo2.toQMetaObject());
        QCOMPARE(mo2.toQMetaObject(), mo1.toQMetaObject());
    }
}

void tst_QScriptEngine::jsIncDecNonObjectProperty()
{
    // This is testing ECMA-262 compliance, not C++ API.

    QScriptEngine eng;
    {
        QScriptValue ret = eng.evaluate("var a; a.n++");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a; a.n--");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a = null; a.n++");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a = null; a.n--");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a; ++a.n");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a; --a.n");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a; a.n += 1");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a; a.n -= 1");
        QVERIFY(ret.isError());
        QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
    }
    {
        QScriptValue ret = eng.evaluate("var a = 'ciao'; a.length++");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 4);
    }
    {
        QScriptValue ret = eng.evaluate("var a = 'ciao'; a.length--");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 4);
    }
    {
        QScriptValue ret = eng.evaluate("var a = 'ciao'; ++a.length");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 5);
    }
    {
        QScriptValue ret = eng.evaluate("var a = 'ciao'; --a.length");
        QVERIFY(ret.isNumber());
        QCOMPARE(ret.toInt32(), 3);
    }
}

void tst_QScriptEngine::installTranslatorFunctions_data()
{
    QTest::addColumn<bool>("useCustomGlobalObject");

    QTest::newRow("Default global object") << false;
    QTest::newRow("Custom global object") << true;
}

void tst_QScriptEngine::installTranslatorFunctions()
{
    QFETCH(bool, useCustomGlobalObject);

    QScriptEngine eng;
    QScriptValue globalOrig = eng.globalObject();
    QScriptValue global;
    if (useCustomGlobalObject) {
        global = eng.newObject();
        eng.setGlobalObject(global);
    } else {
        global = globalOrig;
    }
    QVERIFY(!global.property("qsTranslate").isValid());
    QVERIFY(!global.property("QT_TRANSLATE_NOOP").isValid());
    QVERIFY(!global.property("qsTr").isValid());
    QVERIFY(!global.property("QT_TR_NOOP").isValid());
    QVERIFY(!global.property("qsTrId").isValid());
    QVERIFY(!global.property("QT_TRID_NOOP").isValid());
    QVERIFY(!globalOrig.property("String").property("prototype").property("arg").isValid());

    eng.installTranslatorFunctions();
    QVERIFY(global.property("qsTranslate").isFunction());
    QVERIFY(global.property("QT_TRANSLATE_NOOP").isFunction());
    QVERIFY(global.property("qsTr").isFunction());
    QVERIFY(global.property("QT_TR_NOOP").isFunction());
    QVERIFY(global.property("qsTrId").isFunction());
    QVERIFY(global.property("QT_TRID_NOOP").isFunction());
    QVERIFY(globalOrig.property("String").property("prototype").property("arg").isFunction());

    if (useCustomGlobalObject) {
        QVERIFY(!globalOrig.property("qsTranslate").isValid());
        QVERIFY(!globalOrig.property("QT_TRANSLATE_NOOP").isValid());
        QVERIFY(!globalOrig.property("qsTr").isValid());
        QVERIFY(!globalOrig.property("QT_TR_NOOP").isValid());
        QVERIFY(!globalOrig.property("qsTrId").isValid());
        QVERIFY(!globalOrig.property("QT_TRID_NOOP").isValid());
    }

    {
        QScriptValue ret = eng.evaluate("qsTr('foo')");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
    }
    {
        QScriptValue ret = eng.evaluate("qsTranslate('foo', 'bar')");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
    }
    {
        QScriptValue ret = eng.evaluate("QT_TR_NOOP('foo')");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
    }
    {
        QScriptValue ret = eng.evaluate("QT_TRANSLATE_NOOP('foo', 'bar')");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
    }
    {
        QScriptValue ret = eng.evaluate("'foo%0'.arg('bar')");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("foobar"));
    }
    {
        QScriptValue ret = eng.evaluate("'foo%0'.arg(123)");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("foo123"));
    }
    {
        // Maybe this should throw an error?
        QScriptValue ret = eng.evaluate("'foo%0'.arg()");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString());
    }

    {
        QScriptValue ret = eng.evaluate("qsTrId('foo')");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
    }
    {
        QScriptValue ret = eng.evaluate("QT_TRID_NOOP('foo')");
        QVERIFY(ret.isString());
        QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
    }
    QVERIFY(eng.evaluate("QT_TRID_NOOP()").isUndefined());
}

class TranslationScope
{
public:
    TranslationScope(const QString &fileName)
    {
        translator.load(fileName);
        QCoreApplication::instance()->installTranslator(&translator);
    }
    ~TranslationScope()
    {
        QCoreApplication::instance()->removeTranslator(&translator);
    }

private:
    QTranslator translator;
};

void tst_QScriptEngine::translateScript_data()
{
    QTest::addColumn<QString>("expression");
    QTest::addColumn<QString>("fileName");
    QTest::addColumn<QString>("expectedTranslation");

    QString fileName = QString::fromLatin1("translatable.js");
    // Top-level
    QTest::newRow("qsTr('One')@translatable.js")
            << QString::fromLatin1("qsTr('One')") << fileName << QString::fromLatin1("En");
    QTest::newRow("qsTr('Hello')@translatable.js")
            << QString::fromLatin1("qsTr('Hello')") << fileName << QString::fromLatin1("Hallo");
    // From function
    QTest::newRow("(function() { return qsTr('One'); })()@translatable.js")
            << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName << QString::fromLatin1("En");
    QTest::newRow("(function() { return qsTr('Hello'); })()@translatable.js")
            << QString::fromLatin1("(function() { return qsTr('Hello'); })()") << fileName << QString::fromLatin1("Hallo");
    // From eval
    QTest::newRow("eval('qsTr(\\'One\\')')@translatable.js")
            << QString::fromLatin1("eval('qsTr(\\'One\\')')") << fileName << QString::fromLatin1("En");
    QTest::newRow("eval('qsTr(\\'Hello\\')')@translatable.js")
            << QString::fromLatin1("eval('qsTr(\\'Hello\\')')") << fileName << QString::fromLatin1("Hallo");
    // Plural
    QTest::newRow("qsTr('%n message(s) saved', '', 1)@translatable.js")
            << QString::fromLatin1("qsTr('%n message(s) saved', '', 1)") << fileName << QString::fromLatin1("1 melding lagret");
    QTest::newRow("qsTr('%n message(s) saved', '', 3).arg@translatable.js")
            << QString::fromLatin1("qsTr('%n message(s) saved', '', 3)") << fileName << QString::fromLatin1("3 meldinger lagret");

    // Top-level
    QTest::newRow("qsTranslate('FooContext', 'Two')@translatable.js")
            << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName << QString::fromLatin1("To");
    QTest::newRow("qsTranslate('FooContext', 'Goodbye')@translatable.js")
            << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye')") << fileName << QString::fromLatin1("Farvel");
    // From eval
    QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')@translatable.js")
            << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')") << fileName << QString::fromLatin1("To");
    QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')@translatable.js")
            << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')") << fileName << QString::fromLatin1("Farvel");

    QTest::newRow("qsTranslate('FooContext', 'Goodbye', '')@translatable.js")
            << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '')") << fileName << QString::fromLatin1("Farvel");

    QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 42)@translatable.js")
            << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 42)") << fileName << QString::fromLatin1("Goodbye");

    QTest::newRow("qsTr('One', 'not the same one')@translatable.js")
            << QString::fromLatin1("qsTr('One', 'not the same one')") << fileName << QString::fromLatin1("Enda en");

    QTest::newRow("qsTr('One', 'not the same one', 42)@translatable.js")
            << QString::fromLatin1("qsTr('One', 'not the same one', 42)") << fileName << QString::fromLatin1("One");

    // Plural
    QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 1)@translatable.js")
            << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 1)") << fileName << QString::fromLatin1("1 fooaktig bar funnet");
    QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 2)@translatable.js")
            << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 2)") << fileName << QString::fromLatin1("2 fooaktige barer funnet");

    // Don't exist in translation
    QTest::newRow("qsTr('Three')@translatable.js")
            << QString::fromLatin1("qsTr('Three')") << fileName << QString::fromLatin1("Three");
    QTest::newRow("qsTranslate('FooContext', 'So long')@translatable.js")
            << QString::fromLatin1("qsTranslate('FooContext', 'So long')") << fileName << QString::fromLatin1("So long");
    QTest::newRow("qsTranslate('BarContext', 'Goodbye')@translatable.js")
            << QString::fromLatin1("qsTranslate('BarContext', 'Goodbye')") << fileName << QString::fromLatin1("Goodbye");

    // Translate strings from the second script (translatable2.js)

    QString fileName2 = QString::fromLatin1("translatable2.js");
    QTest::newRow("qsTr('Three')@translatable2.js")
            << QString::fromLatin1("qsTr('Three')") << fileName2 << QString::fromLatin1("Tre");
    QTest::newRow("qsTr('Happy birthday!')@translatable2.js")
            << QString::fromLatin1("qsTr('Happy birthday!')") << fileName2 << QString::fromLatin1("Gratulerer med dagen!");

    // Not translated because translation is only in translatable.js
    QTest::newRow("qsTr('One')@translatable2.js")
            << QString::fromLatin1("qsTr('One')") << fileName2 << QString::fromLatin1("One");
    QTest::newRow("(function() { return qsTr('One'); })()@translatable2.js")
            << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName2 << QString::fromLatin1("One");

    // For qsTranslate() the filename shouldn't matter
    QTest::newRow("qsTranslate('FooContext', 'Two')@translatable2.js")
            << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName2 << QString::fromLatin1("To");
    QTest::newRow("qsTranslate('BarContext', 'Congratulations!')@translatable.js")
            << QString::fromLatin1("qsTranslate('BarContext', 'Congratulations!')") << fileName << QString::fromLatin1("Gratulerer!");
}

void tst_QScriptEngine::translateScript()
{
    QFETCH(QString, expression);
    QFETCH(QString, fileName);
    QFETCH(QString, expectedTranslation);

    QScriptEngine engine;

    TranslationScope tranScope(":/translations/translatable_la");
    engine.installTranslatorFunctions();

    QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
    QVERIFY(!engine.hasUncaughtException());
}

void tst_QScriptEngine::translateScript_crossScript()
{
    QScriptEngine engine;
    TranslationScope tranScope(":/translations/translatable_la");
    engine.installTranslatorFunctions();

    QString fileName = QString::fromLatin1("translatable.js");
    QString fileName2 = QString::fromLatin1("translatable2.js");
    // qsTr() should use the innermost filename as context
    engine.evaluate("function foo(s) { return bar(s); }", fileName);
    engine.evaluate("function bar(s) { return qsTr(s); }", fileName2);
    QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Tre"));
    QCOMPARE(engine.evaluate("bar('Three')", fileName).toString(), QString::fromLatin1("Tre"));
    QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("One"));

    engine.evaluate("function foo(s) { return bar(s); }", fileName2);
    engine.evaluate("function bar(s) { return qsTr(s); }", fileName);
    QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Three"));
    QCOMPARE(engine.evaluate("bar('One')", fileName).toString(), QString::fromLatin1("En"));
    QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("En"));
}

static QScriptValue callQsTr(QScriptContext *ctx, QScriptEngine *eng)
{
    return eng->globalObject().property("qsTr").call(ctx->thisObject(), ctx->argumentsObject());
}

void tst_QScriptEngine::translateScript_callQsTrFromNative()
{
    QScriptEngine engine;
    TranslationScope tranScope(":/translations/translatable_la");
    engine.installTranslatorFunctions();

    QString fileName = QString::fromLatin1("translatable.js");
    QString fileName2 = QString::fromLatin1("translatable2.js");
    // Calling qsTr() from a native function
    engine.globalObject().setProperty("qsTrProxy", engine.newFunction(callQsTr));
    QCOMPARE(engine.evaluate("qsTrProxy('One')", fileName).toString(), QString::fromLatin1("En"));
    QCOMPARE(engine.evaluate("qsTrProxy('One')", fileName2).toString(), QString::fromLatin1("One"));
    QCOMPARE(engine.evaluate("qsTrProxy('Three')", fileName).toString(), QString::fromLatin1("Three"));
    QCOMPARE(engine.evaluate("qsTrProxy('Three')", fileName2).toString(), QString::fromLatin1("Tre"));
}

void tst_QScriptEngine::translateScript_trNoOp()
{
    QScriptEngine engine;
    TranslationScope tranScope(":/translations/translatable_la");
    engine.installTranslatorFunctions();

    QVERIFY(engine.evaluate("QT_TR_NOOP()").isUndefined());
    QCOMPARE(engine.evaluate("QT_TR_NOOP('One')").toString(), QString::fromLatin1("One"));

    QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP()").isUndefined());
    QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP('FooContext')").isUndefined());
    QCOMPARE(engine.evaluate("QT_TRANSLATE_NOOP('FooContext', 'Two')").toString(), QString::fromLatin1("Two"));
}

void tst_QScriptEngine::translateScript_callQsTrFromCpp()
{
    QScriptEngine engine;
    TranslationScope tranScope(":/translations/translatable_la");
    engine.installTranslatorFunctions();

    // There is no context, but it shouldn't crash
    QCOMPARE(engine.globalObject().property("qsTr").call(
             QScriptValue(), QScriptValueList() << "One").toString(), QString::fromLatin1("One"));
}

void tst_QScriptEngine::translateWithInvalidArgs_data()
{
    QTest::addColumn<QString>("expression");
    QTest::addColumn<QString>("expectedError");

    QTest::newRow("qsTr()")  << "qsTr()" << "Error: qsTr() requires at least one argument";
    QTest::newRow("qsTr(123)")  << "qsTr(123)" << "Error: qsTr(): first argument (text) must be a string";
    QTest::newRow("qsTr('foo', 123)")  << "qsTr('foo', 123)" << "Error: qsTr(): second argument (comment) must be a string";
    QTest::newRow("qsTr('foo', 'bar', 'baz')")  << "qsTr('foo', 'bar', 'baz')" << "Error: qsTr(): third argument (n) must be a number";
    QTest::newRow("qsTr('foo', 'bar', true)")  << "qsTr('foo', 'bar', true)" << "Error: qsTr(): third argument (n) must be a number";

    QTest::newRow("qsTranslate()")  << "qsTranslate()" << "Error: qsTranslate() requires at least two arguments";
    QTest::newRow("qsTranslate('foo')")  << "qsTranslate('foo')" << "Error: qsTranslate() requires at least two arguments";
    QTest::newRow("qsTranslate(123, 'foo')")  << "qsTranslate(123, 'foo')" << "Error: qsTranslate(): first argument (context) must be a string";
    QTest::newRow("qsTranslate('foo', 123)")  << "qsTranslate('foo', 123)" << "Error: qsTranslate(): second argument (text) must be a string";
    QTest::newRow("qsTranslate('foo', 'bar', 123)")  << "qsTranslate('foo', 'bar', 123)" << "Error: qsTranslate(): third argument (comment) must be a string";
    QTest::newRow("qsTranslate('foo', 'bar', 'baz', 'zab', 'rab')")  << "qsTranslate('foo', 'bar', 'baz', 'zab', 'rab')" << "Error: qsTranslate(): fifth argument (n) must be a number";

    QTest::newRow("qsTrId()")  << "qsTrId()" << "Error: qsTrId() requires at least one argument";
    QTest::newRow("qsTrId(123)")  << "qsTrId(123)" << "TypeError: qsTrId(): first argument (id) must be a string";
    QTest::newRow("qsTrId('foo', 'bar')")  << "qsTrId('foo', 'bar')" << "TypeError: qsTrId(): second argument (n) must be a number";
}

void tst_QScriptEngine::translateWithInvalidArgs()
{
    QFETCH(QString, expression);
    QFETCH(QString, expectedError);
    QScriptEngine engine;
    engine.installTranslatorFunctions();
    QScriptValue result = engine.evaluate(expression);
    QVERIFY(result.isError());
    QCOMPARE(result.toString(), expectedError);
}

void tst_QScriptEngine::translationContext_data()
{
    QTest::addColumn<QString>("path");
    QTest::addColumn<QString>("text");
    QTest::addColumn<QString>("expectedTranslation");

    QTest::newRow("translatable.js")  << "translatable.js" << "One" << "En";
    QTest::newRow("/translatable.js")  << "/translatable.js" << "One" << "En";
    QTest::newRow("/foo/translatable.js")  << "/foo/translatable.js" << "One" << "En";
    QTest::newRow("/foo/bar/translatable.js")  << "/foo/bar/translatable.js" << "One" << "En";
    QTest::newRow("./translatable.js")  << "./translatable.js" << "One" << "En";
    QTest::newRow("../translatable.js")  << "../translatable.js" << "One" << "En";
    QTest::newRow("foo/translatable.js")  << "foo/translatable.js" << "One" << "En";
    QTest::newRow("file:///home/qt/translatable.js")  << "file:///home/qt/translatable.js" << "One" << "En";
    QTest::newRow(":/resources/translatable.js")  << ":/resources/translatable.js" << "One" << "En";
    QTest::newRow("/translatable.js.foo")  << "/translatable.js.foo" << "One" << "En";
    QTest::newRow("/translatable.txt")  << "/translatable.txt" << "One" << "En";
    QTest::newRow("translatable")  << "translatable" << "One" << "En";
    QTest::newRow("foo/translatable")  << "foo/translatable" << "One" << "En";

    QTest::newRow("native separators")
        << (QDir::toNativeSeparators(QDir::currentPath()) + QDir::separator() + "translatable.js")
        << "One" << "En";

    QTest::newRow("translatable.js/")  << "translatable.js/" << "One" << "One";
    QTest::newRow("nosuchscript.js")  << "" << "One" << "One";
    QTest::newRow("(empty)")  << "" << "One" << "One";
}

void tst_QScriptEngine::translationContext()
{
    TranslationScope tranScope(":/translations/translatable_la");

    QScriptEngine engine;
    engine.installTranslatorFunctions();

    QFETCH(QString, path);
    QFETCH(QString, text);
    QFETCH(QString, expectedTranslation);
    QScriptValue ret = engine.evaluate(QString::fromLatin1("qsTr('%0')").arg(text), path);
    QVERIFY(ret.isString());
    QCOMPARE(ret.toString(), expectedTranslation);
}

void tst_QScriptEngine::translateScriptIdBased()
{
    QScriptEngine engine;

    TranslationScope tranScope(":/translations/idtranslatable_la");
    engine.installTranslatorFunctions();

    QString fileName = QString::fromLatin1("idtranslatable.js");

    QHash<QString, QString> expectedTranslations;
    expectedTranslations["qtn_foo_bar"] = "First string";
    expectedTranslations["qtn_needle"] = "Second string";
    expectedTranslations["qtn_haystack"] = "Third string";
    expectedTranslations["qtn_bar_baz"] = "Fourth string";

    QHash<QString, QString>::const_iterator it;
    for (it = expectedTranslations.constBegin(); it != expectedTranslations.constEnd(); ++it) {
        for (int x = 0; x < 2; ++x) {
            QString fn;
            if (x)
                fn = fileName;
            // Top-level
            QCOMPARE(engine.evaluate(QString::fromLatin1("qsTrId('%0')")
                                     .arg(it.key()), fn).toString(),
                     it.value());
            QCOMPARE(engine.evaluate(QString::fromLatin1("QT_TRID_NOOP('%0')")
                                     .arg(it.key()), fn).toString(),
                     it.key());
            // From function
            QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return qsTrId('%0'); })()")
                                     .arg(it.key()), fn).toString(),
                     it.value());
            QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return QT_TRID_NOOP('%0'); })()")
                                     .arg(it.key()), fn).toString(),
                     it.key());
        }
    }

    // Plural form
    QCOMPARE(engine.evaluate("qsTrId('qtn_bar_baz', 10)").toString(),
             QString::fromLatin1("10 fooish bar(s) found"));
    QCOMPARE(engine.evaluate("qsTrId('qtn_foo_bar', 10)").toString(),
             QString::fromLatin1("qtn_foo_bar")); // Doesn't have plural
}

// How to add a new test row:
// - Find a nice list of Unicode characters to choose from
// - Write source string/context/comment in .js using Unicode escape sequences (\uABCD)
// - Update corresponding .ts file (e.g. lupdate foo.js -ts foo.ts -codecfortr UTF-8)
// - Enter translation in Linguist
// - Update corresponding .qm file (e.g. lrelease foo.ts)
// - Evaluate script that performs translation; make sure the correct result is returned
//   (e.g. by setting the resulting string as the text of a QLabel and visually verifying
//   that it looks the same as what you entered in Linguist :-) )
// - Generate the expectedTranslation column data using toUtf8().toHex()
void tst_QScriptEngine::translateScriptUnicode_data()
{
    QTest::addColumn<QString>("expression");
    QTest::addColumn<QString>("fileName");
    QTest::addColumn<QString>("expectedTranslation");

    QString fileName = QString::fromLatin1("translatable-unicode.js");
    QTest::newRow("qsTr('H\\u2082O')@translatable-unicode.js")
            << QString::fromLatin1("qsTr('H\\u2082O')") << fileName << QString::fromUtf8("\xcd\xbb\xcd\xbc\xcd\xbd");
    QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')@translatable-unicode.js")
            << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << fileName << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2");
    QTest::newRow("qsTr('\\u0391\\u0392\\u0393')@translatable-unicode.js")
            << QString::fromLatin1("qsTr('\\u0391\\u0392\\u0393')") << fileName << QString::fromUtf8("\xd3\x9c\xd2\xb4\xd1\xbc");
    QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')@translatable-unicode.js")
            << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')") << fileName << QString::fromUtf8("\xd8\xae\xd8\xb3\xd8\xb3");
    QTest::newRow("qsTr('H\\u2082O', 'not the same H\\u2082O')@translatable-unicode.js")
            << QString::fromLatin1("qsTr('H\\u2082O', 'not the same H\\u2082O')") << fileName << QString::fromUtf8("\xd4\xb6\xd5\x8a\xd5\x92");
    QTest::newRow("qsTr('H\\u2082O')")
            << QString::fromLatin1("qsTr('H\\u2082O')") << QString() << QString::fromUtf8("\x48\xe2\x82\x82\x4f");
    QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')")
            << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << QString() << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2");
}

void tst_QScriptEngine::translateScriptUnicode()
{
    QFETCH(QString, expression);
    QFETCH(QString, fileName);
    QFETCH(QString, expectedTranslation);

    QScriptEngine engine;

    TranslationScope tranScope(":/translations/translatable-unicode");
    engine.installTranslatorFunctions();

    QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
    QVERIFY(!engine.hasUncaughtException());
}

void tst_QScriptEngine::translateScriptUnicodeIdBased_data()
{
    QTest::addColumn<QString>("expression");
    QTest::addColumn<QString>("expectedTranslation");

    QTest::newRow("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1'')")
            << QString::fromLatin1("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1')") << QString::fromUtf8("\xc6\xa7\xc6\xb0\xc6\x88\xc8\xbc\xc8\x9d\xc8\xbf\xc8\x99");
    QTest::newRow("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')")
            << QString::fromLatin1("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')") << QString::fromUtf8("\xc7\xa0\xc8\xa1\xc8\x8b\xc8\x85\xc8\x95");
    QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)")
            << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)") << QString::fromUtf8("\x31\x30\x20\xc6\x92\xc6\xa1\xc7\x92\x28\xc8\x99\x29");
    QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')")
            << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')") << QString::fromUtf8("\xc6\x91\xc6\xb0\xc7\xb9");
    QTest::newRow("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')")
            << QString::fromLatin1("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')") << QString::fromUtf8("\xc7\x8d\xc6\x80\xc6\xa8\xc6\x90\xc6\x9e\xc6\xab");
}

void tst_QScriptEngine::translateScriptUnicodeIdBased()
{
    QFETCH(QString, expression);
    QFETCH(QString, expectedTranslation);

    QScriptEngine engine;

    TranslationScope tranScope(":/translations/idtranslatable-unicode");
    engine.installTranslatorFunctions();

    QCOMPARE(engine.evaluate(expression).toString(), expectedTranslation);
    QVERIFY(!engine.hasUncaughtException());
}

void tst_QScriptEngine::translateFromBuiltinCallback()
{
    QScriptEngine eng;
    eng.installTranslatorFunctions();

    // Callback has no translation context.
    eng.evaluate("function foo() { qsTr('foo'); }");

    // Stack at translation time will be:
    // qsTr, foo, forEach, global
    // qsTr() needs to walk to the outer-most (global) frame before it finds
    // a translation context, and this should not crash.
    eng.evaluate("[10,20].forEach(foo)", "script.js");
}

void tst_QScriptEngine::functionScopes()
{
    QScriptEngine eng;
    {
        // top-level functions have only the global object in their scope
        QScriptValue fun = eng.evaluate("(function() {})");
        QVERIFY(fun.isFunction());
        QEXPECT_FAIL("", "QScriptValue::scope() is internal, not implemented", Abort);
        QVERIFY(fun.scope().isObject());
        QVERIFY(fun.scope().strictlyEquals(eng.globalObject()));
        QVERIFY(!eng.globalObject().scope().isValid());
    }
    {
        QScriptValue fun = eng.globalObject().property("Object");
        QVERIFY(fun.isFunction());
        // native built-in functions don't have scope
        QVERIFY(!fun.scope().isValid());
    }
    {
        // closure
        QScriptValue fun = eng.evaluate("(function(arg) { var foo = arg; return function() { return foo; }; })(123)");
        QVERIFY(fun.isFunction());
        {
            QScriptValue ret = fun.call();
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 123);
        }
        QScriptValue scope = fun.scope();
        QVERIFY(scope.isObject());
        {
            QScriptValue ret = scope.property("foo");
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 123);
            QCOMPARE(scope.propertyFlags("foo"), QScriptValue::Undeletable);
        }
        {
            QScriptValue ret = scope.property("arg");
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 123);
            QCOMPARE(scope.propertyFlags("arg"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
        }

        scope.setProperty("foo", 456);
        {
            QScriptValue ret = fun.call();
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 456);
        }

        scope = scope.scope();
        QVERIFY(scope.isObject());
        QVERIFY(scope.strictlyEquals(eng.globalObject()));
    }
}

static QScriptValue counter_inner(QScriptContext *ctx, QScriptEngine *)
{
     QScriptValue outerAct = ctx->callee().scope();
     double count = outerAct.property("count").toNumber();
     outerAct.setProperty("count", count+1);
     return count;
}

static QScriptValue counter(QScriptContext *ctx, QScriptEngine *eng)
{
     QScriptValue act = ctx->activationObject();
     act.setProperty("count", ctx->argument(0).toInt32());
     QScriptValue result = eng->newFunction(counter_inner);
     result.setScope(act);
     return result;
}

static QScriptValue counter_hybrid(QScriptContext *ctx, QScriptEngine *eng)
{
     QScriptValue act = ctx->activationObject();
     act.setProperty("count", ctx->argument(0).toInt32());
     return eng->evaluate("(function() { return count++; })");
}

void tst_QScriptEngine::nativeFunctionScopes()
{
    QScriptEngine eng;
    {
        QScriptValue fun = eng.newFunction(counter);
        QScriptValue cnt = fun.call(QScriptValue(), QScriptValueList() << 123);
        QVERIFY(cnt.isFunction());
        {
            QScriptValue ret = cnt.call();
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 123);
        }
    }
    {
        QScriptValue fun = eng.newFunction(counter_hybrid);
        QScriptValue cnt = fun.call(QScriptValue(), QScriptValueList() << 123);
        QVERIFY(cnt.isFunction());
        {
            QScriptValue ret = cnt.call();
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 123);
        }
    }

    //from http://doc.trolltech.com/latest/qtscript.html#nested-functions-and-the-scope-chain
    {
        QScriptEngine eng;
        eng.evaluate("function counter() { var count = 0; return function() { return count++; } }\n"
                     "var c1 = counter();  var c2 = counter(); ");
        QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
        QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
        QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
        QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
        QVERIFY(!eng.hasUncaughtException());
    }
    {
        QScriptEngine eng;
        eng.globalObject().setProperty("counter", eng.newFunction(counter));
        eng.evaluate("var c1 = counter();  var c2 = counter(); ");
        QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
        QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
        QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
        QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
        QVERIFY(!eng.hasUncaughtException());
    }
    {
        QScriptEngine eng;
        eng.globalObject().setProperty("counter", eng.newFunction(counter_hybrid));
        eng.evaluate("var c1 = counter();  var c2 = counter(); ");
        QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
        QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
        QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
        QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
        QVERIFY(!eng.hasUncaughtException());
    }
}

static QScriptValue createProgram(QScriptContext *ctx, QScriptEngine *eng)
{
    QString code = ctx->argument(0).toString();
    QScriptProgram result(code);
    return qScriptValueFromValue(eng, result);
}

void tst_QScriptEngine::evaluateProgram()
{
    QScriptEngine eng;

    {
        QString code("1 + 2");
        QString fileName("hello.js");
        int lineNumber(123);
        QScriptProgram program(code, fileName, lineNumber);
        QVERIFY(!program.isNull());
        QCOMPARE(program.sourceCode(), code);
        QCOMPARE(program.fileName(), fileName);
        QCOMPARE(program.firstLineNumber(), lineNumber);

        QScriptValue expected = eng.evaluate(code);
        for (int x = 0; x < 10; ++x) {
            QScriptValue ret = eng.evaluate(program);
            QVERIFY(ret.equals(expected));
        }

        // operator=
        QScriptProgram sameProgram = program;
        QVERIFY(sameProgram == program);
        QVERIFY(eng.evaluate(sameProgram).equals(expected));

        // copy constructor
        QScriptProgram sameProgram2(program);
        QVERIFY(sameProgram2 == program);
        QVERIFY(eng.evaluate(sameProgram2).equals(expected));

        QScriptProgram differentProgram("2 + 3");
        QVERIFY(differentProgram != program);
        QVERIFY(!eng.evaluate(differentProgram).equals(expected));
    }
}

void tst_QScriptEngine::evaluateProgram_customScope()
{
    QScriptEngine eng;
    {
        QScriptProgram program("a");
        QVERIFY(!program.isNull());
        {
            QScriptValue ret = eng.evaluate(program);
            QVERIFY(ret.isError());
            QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: a"));
        }

        QScriptValue obj = eng.newObject();
        obj.setProperty("a", 123);
        QScriptContext *ctx = eng.currentContext();
        ctx->pushScope(obj);
        {
            QScriptValue ret = eng.evaluate(program);
            QVERIFY(!ret.isError());
            QVERIFY(ret.equals(obj.property("a")));
        }

        obj.setProperty("a", QScriptValue());
        {
            QScriptValue ret = eng.evaluate(program);
            QVERIFY(ret.isError());
        }

        QScriptValue obj2 = eng.newObject();
        obj2.setProperty("a", 456);
        ctx->pushScope(obj2);
        {
            QScriptValue ret = eng.evaluate(program);
            QVERIFY(!ret.isError());
            QVERIFY(ret.equals(obj2.property("a")));
        }

        ctx->popScope();
    }
}

void tst_QScriptEngine::evaluateProgram_closure()
{
    QScriptEngine eng;
    {
        QScriptProgram program("(function() { var count = 0; return function() { return count++; }; })");
        QVERIFY(!program.isNull());
        QScriptValue createCounter = eng.evaluate(program);
        QVERIFY(createCounter.isFunction());
        QScriptValue counter = createCounter.call();
        QVERIFY(counter.isFunction());
        {
            QScriptValue ret = counter.call();
            QVERIFY(ret.isNumber());
        }
        QScriptValue counter2 = createCounter.call();
        QVERIFY(counter2.isFunction());
        QVERIFY(!counter2.equals(counter));
        {
            QScriptValue ret = counter2.call();
            QVERIFY(ret.isNumber());
        }
    }
}

void tst_QScriptEngine::evaluateProgram_executeLater()
{
    QScriptEngine eng;
    // Program created in a function call, then executed later
    {
        QScriptValue fun = eng.newFunction(createProgram);
        QScriptProgram program = qscriptvalue_cast<QScriptProgram>(
            fun.call(QScriptValue(), QScriptValueList() << "a + 1"));
        QVERIFY(!program.isNull());
        eng.globalObject().setProperty("a", QScriptValue());
        {
            QScriptValue ret = eng.evaluate(program);
            QVERIFY(ret.isError());
            QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: a"));
        }
        eng.globalObject().setProperty("a", 122);
        {
            QScriptValue ret = eng.evaluate(program);
            QVERIFY(!ret.isError());
            QVERIFY(ret.isNumber());
            QCOMPARE(ret.toInt32(), 123);
        }
    }
}

void tst_QScriptEngine::evaluateProgram_multipleEngines()
{
    QScriptEngine eng;
    {
        QString code("1 + 2");
        QScriptProgram program(code);
        QVERIFY(!program.isNull());
        double expected = eng.evaluate(program).toNumber();
        for (int x = 0; x < 2; ++x) {
            QScriptEngine eng2;
            for (int y = 0; y < 2; ++y) {
                double ret = eng2.evaluate(program).toNumber();
                QCOMPARE(ret, expected);
            }
        }
    }
}

void tst_QScriptEngine::evaluateProgram_empty()
{
    QScriptEngine eng;
    {
        QScriptProgram program;
        QVERIFY(program.isNull());
        QScriptValue ret = eng.evaluate(program);
        QVERIFY(!ret.isValid());
    }
}

void tst_QScriptEngine::collectGarbageAfterConnect()
{
    // QTBUG-6366
    QScriptEngine engine;
    QPointer<QWidget> widget = new QWidget;
    engine.globalObject().setProperty(
        "widget", engine.newQObject(widget, QScriptEngine::ScriptOwnership));
    QVERIFY(engine.evaluate("widget.customContextMenuRequested.connect(\n"
                            "  function() { print('hello'); }\n"
                            ");")
            .isUndefined());
    QVERIFY(widget != 0);
    engine.evaluate("widget = null;");
    // The connection should not keep the widget alive.
    collectGarbage_helper(engine);
    QVERIFY(widget == 0);
}

void tst_QScriptEngine::collectGarbageAfterNativeArguments()
{
    // QTBUG-17788
    QScriptEngine eng;
    QScriptContext *ctx = eng.pushContext();
    QScriptValue arguments = ctx->argumentsObject();
    // Shouldn't crash when marking the arguments object.
    collectGarbage_helper(eng);
}

static QScriptValue constructQObjectFromThisObject(QScriptContext *ctx, QScriptEngine *eng)
{
    if (!ctx->isCalledAsConstructor()) {
        qWarning("%s: ctx->isCalledAsConstructor() returned false", Q_FUNC_INFO);
        return QScriptValue();
    }
    return eng->newQObject(ctx->thisObject(), new QObject, QScriptEngine::ScriptOwnership);
}

void tst_QScriptEngine::promoteThisObjectToQObjectInConstructor()
{
    QScriptEngine engine;
    QScriptValue ctor = engine.newFunction(constructQObjectFromThisObject);
    engine.globalObject().setProperty("Ctor", ctor);
    QScriptValue object = engine.evaluate("new Ctor");
    QVERIFY(!object.isError());
    QVERIFY(object.isQObject());
    QVERIFY(object.toQObject() != 0);
    QVERIFY(object.property("objectName").isString());
    QVERIFY(object.property("deleteLater").isFunction());
}

static QRegExp minimal(QRegExp r) { r.setMinimal(true); return r; }

void tst_QScriptEngine::qRegExpInport_data()
{
    QTest::addColumn<QRegExp>("rx");
    QTest::addColumn<QString>("string");
    QTest::addColumn<QString>("matched");

    QTest::newRow("normal")  << QRegExp("(test|foo)") << "test _ foo _ test _ Foo";
    QTest::newRow("normal2")  << QRegExp("(Test|Foo)") << "test _ foo _ test _ Foo";
    QTest::newRow("case insensitive)")  << QRegExp("(test|foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
    QTest::newRow("case insensitive2)")  << QRegExp("(Test|Foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
    QTest::newRow("b(a*)(b*)")  << QRegExp("b(a*)(b*)", Qt::CaseInsensitive) << "aaabbBbaAabaAaababaaabbaaab";
    QTest::newRow("greedy")  << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp2) << "aaaabaaba";
    // this one will fail because we do not support the QRegExp::RegExp in JSC
    //QTest::newRow("not_greedy")  << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp) << "aaaabaaba";
    QTest::newRow("willcard")  << QRegExp("*.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "file.txt";
    QTest::newRow("willcard 2")  << QRegExp("a?b.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "ab.txt abb.rtc acb.txt";
    QTest::newRow("slash")  << QRegExp("g/.*/s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string/string/string";
    QTest::newRow("slash2")  << QRegExp("g / .* / s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string / string / string";
    QTest::newRow("fixed")  << QRegExp("a*aa.a(ba)*a\\ba", Qt::CaseInsensitive, QRegExp::FixedString) << "aa*aa.a(ba)*a\\ba";
    QTest::newRow("fixed insensitive")  << QRegExp("A*A", Qt::CaseInsensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
    QTest::newRow("fixed sensitive")  << QRegExp("A*A", Qt::CaseSensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
    QTest::newRow("html")  << QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2) << "<b>bold</b><i>italic</i><b>bold</b>";
    QTest::newRow("html minimal")  << minimal(QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2)) << "<b>bold</b><i>italic</i><b>bold</b>";
    QTest::newRow("aaa")  << QRegExp("a{2,5}") << "aAaAaaaaaAa";
    QTest::newRow("aaa minimal")  << minimal(QRegExp("a{2,5}")) << "aAaAaaaaaAa";
    QTest::newRow("minimal")  << minimal(QRegExp(".*\\} [*8]")) << "}?} ?} *";
    QTest::newRow(".? minimal")  << minimal(QRegExp(".?")) << ".?";
    QTest::newRow(".+ minimal")  << minimal(QRegExp(".+")) << ".+";
    QTest::newRow("[.?] minimal")  << minimal(QRegExp("[.?]")) << ".?";
    QTest::newRow("[.+] minimal")  << minimal(QRegExp("[.+]")) << ".+";
}

void tst_QScriptEngine::qRegExpInport()
{
    QFETCH(QRegExp, rx);
    QFETCH(QString, string);

    QScriptEngine eng;
    QScriptValue rexp;
    rexp = eng.newRegExp(rx);

    QCOMPARE(rexp.isValid(), true);
    QCOMPARE(rexp.isRegExp(), true);
    QVERIFY(rexp.isFunction());

    QScriptValue func = eng.evaluate("(function(string, regexp) { return string.match(regexp); })");
    QScriptValue result = func.call(QScriptValue(),  QScriptValueList() << string << rexp);

    rx.indexIn(string);
    for (int i = 0; i <= rx.captureCount(); i++)  {
        QCOMPARE(result.property(i).toString(), rx.cap(i));
    }
}

static QByteArray msgDateRoundtripJSQtJS(int i, uint secs,
                                         const QScriptValue &jsDate2,
                                         const QScriptValue &jsDate)
{
    QString result;
    const qsreal diff = jsDate.toNumber() - jsDate2.toNumber();
    QTextStream(&result) << "jsDate2=\"" << jsDate2.toString()
        << "\" (" << jsDate2.toNumber() << "), jsDate=\""
        << jsDate.toString() << "\" (" << jsDate.toNumber()
        << "), diff=" << diff << "\", i=" << i << ", secs=" << secs
        << ", TZ=\"" << qgetenv("TZ") << '"';
    return result.toLocal8Bit();
}

// QScriptValue::toDateTime() returns a local time, whereas JS dates
// are always stored as UTC. Qt Script must respect the current time
// zone, and correctly adjust for daylight saving time that may be in
// effect at a given date (QTBUG-9770).
void tst_QScriptEngine::dateRoundtripJSQtJS()
{
    uint secs = QDateTime(QDate(2009, 1, 1)).toUTC().toTime_t();
    QScriptEngine eng;
    for (int i = 0; i < 8000; ++i) {
        QScriptValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0 * 1000.0)").arg(secs));
        QDateTime qtDate = jsDate.toDateTime();
        QScriptValue jsDate2 = eng.newDate(qtDate);
        QVERIFY2(jsDate2.toNumber() == jsDate.toNumber(),
                 msgDateRoundtripJSQtJS(i, secs, jsDate2, jsDate));
        secs += 2*60*60;
    }
}

void tst_QScriptEngine::dateRoundtripQtJSQt()
{
    QDateTime qtDate = QDateTime(QDate(2009, 1, 1));
    QScriptEngine eng;
    for (int i = 0; i < 8000; ++i) {
        QScriptValue jsDate = eng.newDate(qtDate);
        QDateTime qtDate2 = jsDate.toDateTime();
        if (qtDate2 != qtDate)
            QFAIL(qPrintable(qtDate.toString()));
        qtDate = qtDate.addSecs(2*60*60);
    }
}

static QByteArray msgDateConversionJSQt(int i, uint secs,
                                        const QString &qtUTCDateStr,
                                        const QString &jsUTCDateStr,
                                        const QScriptValue &jsDate)
{
    QString result;
    QTextStream(&result) << "qtUTCDateStr=\"" << qtUTCDateStr
        << "\", jsUTCDateStr=\"" << jsUTCDateStr << "\", jsDate=\""
        << jsDate.toString() << "\", i=" << i << ", secs=" << secs
        << ", TZ=\"" << qgetenv("TZ") << '"';
    return result.toLocal8Bit();
}

void tst_QScriptEngine::dateConversionJSQt()
{
    uint secs = QDateTime(QDate(2009, 1, 1)).toUTC().toTime_t();
    QScriptEngine eng;
    for (int i = 0; i < 8000; ++i) {
        QScriptValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0 * 1000.0)").arg(secs));
        QDateTime qtDate = jsDate.toDateTime();
        QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate);
        QString jsUTCDateStr = jsDate.property("toISOString").call(jsDate).toString();
        jsUTCDateStr.remove(jsUTCDateStr.length() - 5, 4); // get rid of milliseconds (".000")
        QVERIFY2(qtUTCDateStr == jsUTCDateStr,
                 msgDateConversionJSQt(i, secs, qtUTCDateStr, jsUTCDateStr, jsDate));
        secs += 2*60*60;
    }
}

void tst_QScriptEngine::dateConversionQtJS()
{
    QDateTime qtDate = QDateTime(QDate(2009, 1, 1));
    QScriptEngine eng;
    for (int i = 0; i < 8000; ++i) {
        QScriptValue jsDate = eng.newDate(qtDate);
        QString jsUTCDateStr = jsDate.property("toISOString").call(jsDate).toString();
        jsUTCDateStr.remove(jsUTCDateStr.length() - 5, 4); // get rid of milliseconds (".000")
        QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate);
        if (jsUTCDateStr != qtUTCDateStr)
            QFAIL(qPrintable(qtDate.toString()));
        qtDate = qtDate.addSecs(2*60*60);
    }
}

static QScriptValue createAnotherEngine(QScriptContext *, QScriptEngine *)
{
    QScriptEngine eng;
    eng.evaluate("function foo(x, y) { return x + y; }" );
    eng.evaluate("hello = 5; world = 6" );
    return eng.evaluate("foo(hello,world)").toInt32();
}


void tst_QScriptEngine::reentrency()
{
    QScriptEngine eng;
    eng.globalObject().setProperty("foo", eng.newFunction(createAnotherEngine));
    eng.evaluate("function bar() { return foo(); }  hello = 9; function getHello() { return hello; }");
    QCOMPARE(eng.evaluate("foo() + getHello() + foo()").toInt32(), 5+6 + 9 + 5+6);
    QCOMPARE(eng.evaluate("foo").call().toInt32(), 5+6);
    QCOMPARE(eng.evaluate("hello").toInt32(), 9);
    QCOMPARE(eng.evaluate("foo() + hello").toInt32(), 5+6+9);
}

void tst_QScriptEngine::newFixedStaticScopeObject()
{
    // "Static scope objects" is an optimization we do for QML.
    // It enables the creation of JS objects that can guarantee to the
    // compiler that no properties will be added or removed. This enables
    // the compiler to generate a very simple (fast) property access, as
    // opposed to a full virtual lookup. Due to the inherent use of scope
    // chains in QML, this can make a huge difference (10x improvement for
    // benchmark in QTBUG-8576).
    // Ideally we would not need a special object type for this, and the
    // VM would dynamically optimize it to be fast...
    // See also QScriptEngine benchmark.

    QScriptEngine eng;
    static const int propertyCount = 4;
    QString names[] = { "foo", "bar", "baz", "Math" };
    QScriptValue values[] = { 123, "ciao", true, false };
    QScriptValue::PropertyFlags flags[] = { QScriptValue::Undeletable,
                                            QScriptValue::ReadOnly | QScriptValue::Undeletable,
                                            QScriptValue::SkipInEnumeration | QScriptValue::Undeletable,
                                            QScriptValue::Undeletable };
    QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount, names, values, flags);

    // Query property.
    for (int i = 0; i < propertyCount; ++i) {
        for (int x = 0; x < 2; ++x) {
            if (x) {
                // Properties can't be deleted.
                scope.setProperty(names[i], QScriptValue());
            }
            QVERIFY(scope.property(names[i]).equals(values[i]));
            QCOMPARE(scope.propertyFlags(names[i]), flags[i]);
        }
    }

    // Property that doesn't exist.
    QVERIFY(!scope.property("noSuchProperty").isValid());
    QCOMPARE(scope.propertyFlags("noSuchProperty"), QScriptValue::PropertyFlags());

    // Write to writable property.
    {
        QScriptValue oldValue = scope.property("foo");
        QVERIFY(oldValue.isNumber());
        QScriptValue newValue = oldValue.toNumber() * 2;
        scope.setProperty("foo", newValue);
        QVERIFY(scope.property("foo").equals(newValue));
        scope.setProperty("foo", oldValue);
        QVERIFY(scope.property("foo").equals(oldValue));
    }

    // Write to read-only property.
    scope.setProperty("bar", 456);
    QVERIFY(scope.property("bar").equals("ciao"));

    // Iterate.
    {
        QScriptValueIterator it(scope);
        QSet<QString> iteratedNames;
        while (it.hasNext()) {
            it.next();
            iteratedNames.insert(it.name());
        }
        for (int i = 0; i < propertyCount; ++i)
            QVERIFY(iteratedNames.contains(names[i]));
    }

    // Push it on the scope chain of a new context.
    QScriptContext *ctx = eng.pushContext();
    ctx->pushScope(scope);
    QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
    QVERIFY(ctx->activationObject().equals(scope));

    // Read property from JS.
    for (int i = 0; i < propertyCount; ++i) {
        for (int x = 0; x < 2; ++x) {
            if (x) {
                // Property can't be deleted from JS.
                QScriptValue ret = eng.evaluate(QString::fromLatin1("delete %0").arg(names[i]));
                QVERIFY(ret.equals(false));
            }
            QVERIFY(eng.evaluate(names[i]).equals(values[i]));
        }
    }

    // Property that doesn't exist.
    QVERIFY(eng.evaluate("noSuchProperty").equals("ReferenceError: Can't find variable: noSuchProperty"));

    // Write property from JS.
    {
        QScriptValue oldValue = eng.evaluate("foo");
        QVERIFY(oldValue.isNumber());
        QScriptValue newValue = oldValue.toNumber() * 2;
        QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
        scope.setProperty("foo", oldValue);
        QVERIFY(eng.evaluate("foo").equals(oldValue));
    }

    // Write to read-only property.
    QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));

    // Create a closure and return properties from there.
    {
        QScriptValue props = eng.evaluate("(function() { var baz = 'shadow'; return [foo, bar, baz, Math, Array]; })()");
        QVERIFY(props.isArray());
        // "foo" and "bar" come from scope object.
        QVERIFY(props.property(0).equals(scope.property("foo")));
        QVERIFY(props.property(1).equals(scope.property("bar")));
        // "baz" shadows property in scope object.
        QVERIFY(props.property(2).equals("shadow"));
        // "Math" comes from scope object, and shadows Global Object's "Math".
        QVERIFY(props.property(3).equals(scope.property("Math")));
        QVERIFY(!props.property(3).equals(eng.globalObject().property("Math")));
        // "Array" comes from Global Object.
        QVERIFY(props.property(4).equals(eng.globalObject().property("Array")));
    }

    // As with normal JS, assigning to an undefined variable will create
    // the property on the Global Object, not the inner scope.
    QVERIFY(!eng.globalObject().property("newProperty").isValid());
    QVERIFY(eng.evaluate("(function() { newProperty = 789; })()").isUndefined());
    QVERIFY(!scope.property("newProperty").isValid());
    QVERIFY(eng.globalObject().property("newProperty").isNumber());

    // Nested static scope.
    {
        static const int propertyCount2 = 2;
        QString names2[] = { "foo", "hum" };
        QScriptValue values2[] = { 321, "hello" };
        QScriptValue::PropertyFlags flags2[] = { QScriptValue::Undeletable,
                                                 QScriptValue::ReadOnly | QScriptValue::Undeletable };
        QScriptValue scope2 = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount2, names2, values2, flags2);
        ctx->pushScope(scope2);

        // "foo" shadows scope.foo.
        QVERIFY(eng.evaluate("foo").equals(scope2.property("foo")));
        QVERIFY(!eng.evaluate("foo").equals(scope.property("foo")));
        // "hum" comes from scope2.
        QVERIFY(eng.evaluate("hum").equals(scope2.property("hum")));
        // "Array" comes from Global Object.
        QVERIFY(eng.evaluate("Array").equals(eng.globalObject().property("Array")));

        ctx->popScope();
    }

    QScriptValue fun = eng.evaluate("(function() { return foo; })");
    QVERIFY(fun.isFunction());
    eng.popContext();
    // Function's scope chain persists after popContext().
    QVERIFY(fun.call().equals(scope.property("foo")));
}

void tst_QScriptEngine::newGrowingStaticScopeObject()
{
    // The main use case for a growing static scope object is to set it as
    // the activation object of a QScriptContext, so that all JS variable
    // declarations end up in that object. It needs to be "growable" since
    // we don't know in advance how many variables a script will declare.

    QScriptEngine eng;
    QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng);

    // Initially empty.
    QVERIFY(!QScriptValueIterator(scope).hasNext());
    QVERIFY(!scope.property("foo").isValid());

    // Add a static property.
    scope.setProperty("foo", 123);
    QVERIFY(scope.property("foo").equals(123));
    QCOMPARE(scope.propertyFlags("foo"), QScriptValue::Undeletable);

    // Modify existing property.
    scope.setProperty("foo", 456);
    QVERIFY(scope.property("foo").equals(456));

    // Add a read-only property.
    scope.setProperty("bar", "ciao", QScriptValue::ReadOnly);
    QVERIFY(scope.property("bar").equals("ciao"));
    QCOMPARE(scope.propertyFlags("bar"), QScriptValue::ReadOnly | QScriptValue::Undeletable);

    // Attempt to modify read-only property.
    scope.setProperty("bar", "hello");
    QVERIFY(scope.property("bar").equals("ciao"));

    // Properties can't be deleted.
    scope.setProperty("foo", QScriptValue());
    QVERIFY(scope.property("foo").equals(456));
    scope.setProperty("bar", QScriptValue());
    QVERIFY(scope.property("bar").equals("ciao"));

    // Iterate.
    {
        QScriptValueIterator it(scope);
        QSet<QString> iteratedNames;
        while (it.hasNext()) {
            it.next();
            iteratedNames.insert(it.name());
        }
        QCOMPARE(iteratedNames.size(), 2);
        QVERIFY(iteratedNames.contains("foo"));
        QVERIFY(iteratedNames.contains("bar"));
    }

    // Push it on the scope chain of a new context.
    QScriptContext *ctx = eng.pushContext();
    ctx->pushScope(scope);
    QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
    QVERIFY(ctx->activationObject().equals(scope));

    // Read property from JS.
    QVERIFY(eng.evaluate("foo").equals(scope.property("foo")));
    QVERIFY(eng.evaluate("bar").equals(scope.property("bar")));

    // Write property from JS.
    {
        QScriptValue oldValue = eng.evaluate("foo");
        QVERIFY(oldValue.isNumber());
        QScriptValue newValue = oldValue.toNumber() * 2;
        QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
        scope.setProperty("foo", oldValue);
        QVERIFY(eng.evaluate("foo").equals(oldValue));
    }

    // Write to read-only property.
    QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));

    // Shadow property.
    QVERIFY(eng.evaluate("Math").equals(eng.globalObject().property("Math")));
    scope.setProperty("Math", "fake Math");
    QVERIFY(eng.evaluate("Math").equals(scope.property("Math")));

    // Variable declarations will create properties on the scope.
    eng.evaluate("var baz = 456");
    QVERIFY(scope.property("baz").equals(456));

    // Function declarations will create properties on the scope.
    eng.evaluate("function fun() { return baz; }");
    QVERIFY(scope.property("fun").isFunction());
    QVERIFY(scope.property("fun").call().equals(scope.property("baz")));

    // Demonstrate the limitation of a growable static scope: Once a function that
    // uses the scope has been compiled, it won't pick up properties that are added
    // to the scope later.
    {
        QScriptValue fun = eng.evaluate("(function() { return futureProperty; })");
        QVERIFY(fun.isFunction());
        QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError")));
        scope.setProperty("futureProperty", "added after the function was compiled");
        // If scope were dynamic, this would return the new property.
        QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError")));
    }

    eng.popContext();
}

QT_BEGIN_NAMESPACE
Q_SCRIPT_DECLARE_QMETAOBJECT(QStandardItemModel, QObject*)
QT_END_NAMESPACE

void tst_QScriptEngine::scriptValueFromQMetaObject()
{
    QScriptEngine eng;
    {
        QScriptValue meta = eng.scriptValueFromQMetaObject<QScriptEngine>();
        QVERIFY(meta.isQMetaObject());
        QCOMPARE(meta.toQMetaObject(), &QScriptEngine::staticMetaObject);
        // Because of missing Q_SCRIPT_DECLARE_QMETAOBJECT() for QScriptEngine.
        QVERIFY(!meta.construct().isValid());
    }
    {
        QScriptValue meta = eng.scriptValueFromQMetaObject<QStandardItemModel>();
        QVERIFY(meta.isQMetaObject());
        QCOMPARE(meta.toQMetaObject(), &QStandardItemModel::staticMetaObject);
        QScriptValue obj = meta.construct(QScriptValueList() << eng.newQObject(&eng));
        QVERIFY(obj.isQObject());
        QStandardItemModel *model = qobject_cast<QStandardItemModel*>(obj.toQObject());
        QVERIFY(model != 0);
        QCOMPARE(model->parent(), (QObject*)&eng);
    }
}

// QTBUG-21896
void tst_QScriptEngine::stringListFromArrayWithEmptyElement()
{
    QScriptEngine eng;
    QCOMPARE(qscriptvalue_cast<QStringList>(eng.evaluate("[,'hello']")),
             QStringList() << "" << "hello");
}

// QTBUG-21993
void tst_QScriptEngine::collectQObjectWithCachedWrapper_data()
{
    QTest::addColumn<QString>("program");
    QTest::addColumn<QString>("ownership");
    QTest::addColumn<bool>("giveParent");
    QTest::addColumn<bool>("shouldBeCollected");

    QString prog1 = QString::fromLatin1("newQObject(ownership, parent)");
    QTest::newRow("unassigned,cpp,no-parent") << prog1 << "cpp" << false << false;
    QTest::newRow("unassigned,cpp,parent") << prog1 << "cpp" << true << false;
    QTest::newRow("unassigned,auto,no-parent") << prog1 << "auto" << false << true;
    QTest::newRow("unassigned,auto,parent") << prog1 << "auto" << true << false;
    QTest::newRow("unassigned,script,no-parent") << prog1 << "script" << false << true;
    QTest::newRow("unassigned,script,parent") << prog1 << "script" << true << true;

    QString prog2 = QString::fromLatin1("myObject = { foo: newQObject(ownership, parent) }; myObject.foo");
    QTest::newRow("global-property-property,cpp,no-parent") << prog2 << "cpp" << false << false;
    QTest::newRow("global-property-property,cpp,parent") << prog2 << "cpp" << true << false;
    QTest::newRow("global-property-property,auto,no-parent") << prog2 << "auto" << false << false;
    QTest::newRow("global-property-property,auto,parent") << prog2 << "auto" << true << false;
    QTest::newRow("global-property-property,script,no-parent") << prog2 << "script" << false << false;
    QTest::newRow("global-property-property,script,parent") << prog2 << "script" << true << false;

}

void tst_QScriptEngine::collectQObjectWithCachedWrapper()
{
    struct Functions {
        static QScriptValue newQObject(QScriptContext *ctx, QScriptEngine *eng)
        {
            QString ownershipString = ctx->argument(0).toString();
            QScriptEngine::ValueOwnership ownership;
            if (ownershipString == "cpp")
                ownership = QScriptEngine::QtOwnership;
            else if (ownershipString == "auto")
                ownership = QScriptEngine::AutoOwnership;
            else if (ownershipString == "script")
                ownership = QScriptEngine::ScriptOwnership;
            else
                return ctx->throwError("Ownership specifier 'cpp', 'auto' or 'script' expected");

            QObject *parent = ctx->argument(1).toQObject();
            return eng->newQObject(new QObject(parent), ownership,
                                   QScriptEngine::PreferExistingWrapperObject);
        }
    };

    QFETCH(QString, program);
    QFETCH(QString, ownership);
    QFETCH(bool, giveParent);
    QFETCH(bool, shouldBeCollected);

    QScriptEngine eng;
    eng.globalObject().setProperty("ownership", ownership);
    eng.globalObject().setProperty("newQObject",
                                   eng.newFunction(Functions::newQObject));

    QObject parent;
    eng.globalObject().setProperty("parent",
                                   giveParent ? eng.newQObject(&parent)
                                              : QScriptValue(QScriptValue::NullValue));

    QPointer<QObject> ptr = eng.evaluate(program).toQObject();
    QVERIFY(ptr != 0);
    QVERIFY(ptr->parent() == (giveParent ? &parent : 0));

    collectGarbage_helper(eng);

    if (ptr && shouldBeCollected)
        QEXPECT_FAIL("", "Test can fail because the garbage collector is conservative", Continue);
    QCOMPARE(ptr == 0, shouldBeCollected);
}

// QTBUG-18188
void tst_QScriptEngine::pushContext_noInheritedScope()
{
    QScriptEngine eng;
    eng.globalObject().setProperty("foo", 123);

    QScriptContext *ctx1 = eng.pushContext();
    QCOMPARE(ctx1->scopeChain().size(), 2);
    ctx1->activationObject().setProperty("foo", 456);
    QCOMPARE(eng.evaluate("foo").toInt32(), 456);

    QScriptContext *ctx2 = eng.pushContext();
    // The parent context's scope should not be inherited.
    QCOMPARE(ctx2->scopeChain().size(), 2);
    QCOMPARE(eng.evaluate("foo").toInt32(), 123);

    ctx2->activationObject().setProperty("foo", 789);
    QCOMPARE(eng.evaluate("foo").toInt32(), 789);

    eng.popContext();
    QCOMPARE(eng.evaluate("foo").toInt32(), 456);

    eng.popContext();
    QCOMPARE(eng.evaluate("foo").toInt32(), 123);
}

QTEST_MAIN(tst_QScriptEngine)
#include "tst_qscriptengine.moc"
