Changeset 62377 in webkit


Ignore:
Timestamp:
Jul 2, 2010 3:37:31 AM (14 years ago)
Author:
Simon Hausmann
Message:

Implement exception reporting in the QtScript API.

Patch by Jedrzej Nowacki <jedrzej.nowacki@nokia.com> on 2010-06-28
Reviewed by Simon Hausmann.

The exception should be accessible through the API by the uncaughtException
function. Functions; hasUncaughtException, clearExceptions, uncaughtExceptionLineNumber,
uncaughtExceptionBacktrace were added to facilitate error checking and debugging.

[Qt] QtScript API should be exceptions aware.
https://bugs.webkit.org/show_bug.cgi?id=41199

  • api/qscriptengine.cpp:

(QScriptEngine::hasUncaughtException):
(QScriptEngine::uncaughtException):
(QScriptEngine::clearExceptions):
(QScriptEngine::uncaughtExceptionLineNumber):
(QScriptEngine::uncaughtExceptionBacktrace):

  • api/qscriptengine.h:
  • api/qscriptengine_p.cpp:

(QScriptEnginePrivate::QScriptEnginePrivate):
(QScriptEnginePrivate::~QScriptEnginePrivate):
(QScriptEnginePrivate::uncaughtException):

  • api/qscriptengine_p.h:

(QScriptEnginePrivate::):
(QScriptEnginePrivate::evaluate):
(QScriptEnginePrivate::hasUncaughtException):
(QScriptEnginePrivate::clearExceptions):
(QScriptEnginePrivate::setException):
(QScriptEnginePrivate::uncaughtExceptionLineNumber):
(QScriptEnginePrivate::uncaughtExceptionBacktrace):

  • api/qscriptvalue_p.h:

(QScriptValuePrivate::toString):
(QScriptValuePrivate::toNumber):
(QScriptValuePrivate::toObject):
(QScriptValuePrivate::equals):
(QScriptValuePrivate::instanceOf):
(QScriptValuePrivate::call):
(QScriptValuePrivate::inherits):

  • tests/qscriptengine/tst_qscriptengine.cpp:

(tst_QScriptEngine::uncaughtException):

Location:
trunk/JavaScriptCore/qt
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/qt/api/qscriptengine.cpp

    r62078 r62377  
    9797
    9898/*!
     99    Returns true if the last script evaluation resulted in an uncaught
     100    exception; otherwise returns false.
     101
     102    The exception state is cleared when evaluate() is called.
     103
     104    \sa uncaughtException(), uncaughtExceptionLineNumber(),
     105      uncaughtExceptionBacktrace()
     106*/
     107bool QScriptEngine::hasUncaughtException() const
     108{
     109    return d_ptr->hasUncaughtException();
     110}
     111
     112/*!
     113    Returns the current uncaught exception, or an invalid QScriptValue
     114    if there is no uncaught exception.
     115
     116    The exception value is typically an \c{Error} object; in that case,
     117    you can call toString() on the return value to obtain an error
     118    message.
     119
     120    \sa hasUncaughtException(), uncaughtExceptionLineNumber(),
     121      uncaughtExceptionBacktrace()
     122*/
     123QScriptValue QScriptEngine::uncaughtException() const
     124{
     125    return QScriptValuePrivate::get(d_ptr->uncaughtException());
     126}
     127
     128/*!
     129    Clears any uncaught exceptions in this engine.
     130
     131    \sa hasUncaughtException()
     132*/
     133void QScriptEngine::clearExceptions()
     134{
     135    d_ptr->clearExceptions();
     136}
     137
     138/*!
     139    Returns the line number where the last uncaught exception occurred.
     140
     141    Line numbers are 1-based, unless a different base was specified as
     142    the second argument to evaluate().
     143
     144    \sa hasUncaughtException(), uncaughtExceptionBacktrace()
     145*/
     146int QScriptEngine::uncaughtExceptionLineNumber() const
     147{
     148    return d_ptr->uncaughtExceptionLineNumber();
     149}
     150
     151/*!
     152    Returns a human-readable backtrace of the last uncaught exception.
     153
     154    Each line is of the form \c{<function-name>(<arguments>)@<file-name>:<line-number>}.
     155
     156    \sa uncaughtException()
     157*/
     158QStringList QScriptEngine::uncaughtExceptionBacktrace() const
     159{
     160    return d_ptr->uncaughtExceptionBacktrace();
     161}
     162
     163/*!
    99164    Runs the garbage collector.
    100165
  • trunk/JavaScriptCore/qt/api/qscriptengine.h

    r62078 r62377  
    4343    QScriptValue evaluate(const QScriptProgram& program);
    4444
     45    bool hasUncaughtException() const;
     46    QScriptValue uncaughtException() const;
     47    void clearExceptions();
     48    int uncaughtExceptionLineNumber() const;
     49    QStringList uncaughtExceptionBacktrace() const;
     50
    4551    void collectGarbage();
    4652    void reportAdditionalMemoryCost(int cost);
  • trunk/JavaScriptCore/qt/api/qscriptengine_p.cpp

    r62078 r62377  
    3232    : q_ptr(const_cast<QScriptEngine*>(engine))
    3333    , m_context(JSGlobalContextCreate(0))
     34    , m_exception(0)
    3435{
    3536}
     
    3738QScriptEnginePrivate::~QScriptEnginePrivate()
    3839{
     40    if (m_exception)
     41        JSValueUnprotect(m_context, m_exception);
    3942    JSGlobalContextRelease(m_context);
    4043}
     
    7881}
    7982
     83QScriptValuePrivate* QScriptEnginePrivate::uncaughtException() const
     84{
     85    return m_exception ? new QScriptValuePrivate(this, m_exception) : new QScriptValuePrivate();
     86}
     87
    8088QScriptValuePrivate* QScriptEnginePrivate::newObject() const
    8189{
     
    8593QScriptValuePrivate* QScriptEnginePrivate::newArray(uint length) const
    8694{
    87     JSObjectRef array = JSObjectMakeArray(m_context, /* argumentCount */ 0, /* arguments */ 0, /* exception */ 0);
     95    JSValueRef exception = 0;
     96    JSObjectRef array = JSObjectMakeArray(m_context, /* argumentCount */ 0, /* arguments */ 0, &exception);
    8897
    89     if (length > 0) {
    90         JSRetainPtr<JSStringRef> lengthRef(Adopt, JSStringCreateWithUTF8CString("length"));
    91         JSObjectSetProperty(m_context, array, lengthRef.get(), JSValueMakeNumber(m_context, length), kJSPropertyAttributeNone, /* exception */ 0);
     98    if (!exception) {
     99        if (length > 0) {
     100            JSRetainPtr<JSStringRef> lengthRef(Adopt, JSStringCreateWithUTF8CString("length"));
     101            // array is an Array instance, so an exception should not occure here.
     102            JSObjectSetProperty(m_context, array, lengthRef.get(), JSValueMakeNumber(m_context, length), kJSPropertyAttributeNone, /* exception */ 0);
     103        }
     104    } else {
     105        setException(exception, NotNullException);
     106        return new QScriptValuePrivate();
    92107    }
    93108
  • trunk/JavaScriptCore/qt/api/qscriptengine_p.h

    r62078 r62377  
    2727#include "qscriptvalue.h"
    2828#include <JavaScriptCore/JavaScript.h>
     29#include <JavaScriptCore/JSRetainPtr.h>
    2930#include <JSBasePrivate.h>
    3031#include <QtCore/qshareddata.h>
    3132#include <QtCore/qstring.h>
     33#include <QtCore/qstringlist.h>
    3234
    3335class QScriptEngine;
     
    4244    ~QScriptEnginePrivate();
    4345
     46    enum SetExceptionFlag {
     47        IgnoreNullException = 0x01,
     48        NotNullException = 0x02,
     49    };
     50
    4451    QScriptSyntaxCheckResultPrivate* checkSyntax(const QString& program);
    4552    QScriptValuePrivate* evaluate(const QString& program, const QString& fileName, int lineNumber);
    4653    QScriptValuePrivate* evaluate(const QScriptProgramPrivate* program);
    4754    inline JSValueRef evaluate(JSStringRef program, JSStringRef fileName, int lineNumber);
     55
     56    inline bool hasUncaughtException() const;
     57    QScriptValuePrivate* uncaughtException() const;
     58    inline void clearExceptions();
     59    inline void setException(JSValueRef exception, const /* SetExceptionFlags */ unsigned flags = IgnoreNullException);
     60    inline int uncaughtExceptionLineNumber() const;
     61    inline QStringList uncaughtExceptionBacktrace() const;
    4862
    4963    inline void collectGarbage();
     
    6781    QScriptEngine* q_ptr;
    6882    JSGlobalContextRef m_context;
     83    JSValueRef m_exception;
    6984};
    7085
     
    7994    JSValueRef exception;
    8095    JSValueRef result = JSEvaluateScript(m_context, program, /* Global Object */ 0, fileName, lineNumber, &exception);
    81     if (!result)
     96    if (!result) {
     97        setException(exception, NotNullException);
    8298        return exception; // returns an exception
     99    }
     100    clearExceptions();
    83101    return result;
     102}
     103
     104bool QScriptEnginePrivate::hasUncaughtException() const
     105{
     106    return m_exception;
     107}
     108
     109void QScriptEnginePrivate::clearExceptions()
     110{
     111    if (m_exception)
     112        JSValueUnprotect(m_context, m_exception);
     113    m_exception = 0;
     114}
     115
     116void QScriptEnginePrivate::setException(JSValueRef exception, const /* SetExceptionFlags */ unsigned flags)
     117{
     118    if (!((flags & NotNullException) || exception))
     119        return;
     120    Q_ASSERT(exception);
     121
     122    if (m_exception)
     123        JSValueUnprotect(m_context, m_exception);
     124    JSValueProtect(m_context, exception);
     125    m_exception = exception;
     126}
     127
     128int QScriptEnginePrivate::uncaughtExceptionLineNumber() const
     129{
     130    if (!hasUncaughtException() || !JSValueIsObject(m_context, m_exception))
     131        return -1;
     132
     133    JSValueRef exception = 0;
     134    JSRetainPtr<JSStringRef> lineNumberPropertyName(Adopt, QScriptConverter::toString("line"));
     135    JSValueRef lineNumber = JSObjectGetProperty(m_context, const_cast<JSObjectRef>(m_exception), lineNumberPropertyName.get(), &exception);
     136    int result = JSValueToNumber(m_context, lineNumber, &exception);
     137    return exception ? -1 : result;
     138}
     139
     140QStringList QScriptEnginePrivate::uncaughtExceptionBacktrace() const
     141{
     142    if (!hasUncaughtException() || !JSValueIsObject(m_context, m_exception))
     143        return QStringList();
     144
     145    JSValueRef exception = 0;
     146    JSRetainPtr<JSStringRef> fileNamePropertyName(Adopt, QScriptConverter::toString("sourceURL"));
     147    JSRetainPtr<JSStringRef> lineNumberPropertyName(Adopt, QScriptConverter::toString("line"));
     148    JSValueRef jsFileName = JSObjectGetProperty(m_context, const_cast<JSObjectRef>(m_exception), fileNamePropertyName.get(), &exception);
     149    JSValueRef jsLineNumber = JSObjectGetProperty(m_context, const_cast<JSObjectRef>(m_exception), lineNumberPropertyName.get(), &exception);
     150    JSRetainPtr<JSStringRef> fileName(Adopt, JSValueToStringCopy(m_context, jsFileName, &exception));
     151    int lineNumber = JSValueToNumber(m_context, jsLineNumber, &exception);
     152    return QStringList(QString::fromLatin1("<anonymous>()@%0:%1")
     153            .arg(QScriptConverter::toString(fileName.get()))
     154            .arg(QScriptConverter::toString(exception ? -1 : lineNumber)));
    84155}
    85156
  • trunk/JavaScriptCore/qt/api/qscriptvalue_p.h

    r62119 r62377  
    446446    case JSPrimitive:
    447447    case JSObject:
    448         JSRetainPtr<JSStringRef> ptr(Adopt, JSValueToStringCopy(*m_engine, *this, /* exception */ 0));
     448        JSValueRef exception = 0;
     449        JSRetainPtr<JSStringRef> ptr(Adopt, JSValueToStringCopy(*m_engine, *this, &exception));
     450        m_engine->setException(exception);
    449451        return QScriptConverter::toString(ptr.get());
    450452    }
     
    460462    case JSPrimitive:
    461463    case JSObject:
    462         return JSValueToNumber(*m_engine, *this, /* exception */ 0);
     464        {
     465            JSValueRef exception = 0;
     466            qsreal result = JSValueToNumber(*m_engine, *this, &exception);
     467            m_engine->setException(exception);
     468            return result;
     469        }
    463470    case CNumber:
    464471        return u.m_number;
     
    589596            if (engine != this->engine())
    590597                qWarning("QScriptEngine::toObject: cannot convert value created in a different engine");
    591             JSObjectRef object = JSValueToObject(*m_engine, *this, /* exception */ 0);
     598            JSValueRef exception = 0;
     599            JSObjectRef object = JSValueToObject(*m_engine, *this, &exception);
    592600            if (object)
    593601                return new QScriptValuePrivate(m_engine.constData(), object);
     602            else
     603                m_engine->setException(exception, QScriptEnginePrivate::NotNullException);
     604
    594605        }
    595606        return new QScriptValuePrivate;
     
    676687    }
    677688
    678     return JSValueIsEqual(*m_engine, *this, *other, /* exception */ 0);
     689    JSValueRef exception = 0;
     690    bool result = JSValueIsEqual(*m_engine, *this, *other, &exception);
     691    m_engine->setException(exception);
     692    return result;
    679693}
    680694
     
    720734    if (!isJSBased() || !other->isObject())
    721735        return false;
    722     return JSValueIsInstanceOfConstructor(*m_engine, *this, *other, /* exception */ 0);
     736    JSValueRef exception = 0;
     737    bool result = JSValueIsInstanceOfConstructor(*m_engine, *this, *other, &exception);
     738    m_engine->setException(exception);
     739    return result;
    723740}
    724741
     
    814831            JSValueRef exception = 0;
    815832            JSValueRef result = JSObjectCallAsFunction(*m_engine, *this, /* thisObject */ 0, argc, argv.constData(), &exception);
    816             if (!result && exception)
     833            if (!result && exception) {
     834                m_engine->setException(exception);
    817835                return new QScriptValuePrivate(engine(), exception);
     836            }
    818837            if (result && !exception)
    819838                return new QScriptValuePrivate(engine(), result);
     
    856875    JSObjectRef globalObject = JSContextGetGlobalObject(*m_engine);
    857876    JSStringRef errorAttrName = QScriptConverter::toString(name);
    858     JSValueRef error = JSObjectGetProperty(*m_engine, globalObject, errorAttrName, /* exception */ 0);
     877    JSValueRef exception = 0;
     878    JSValueRef error = JSObjectGetProperty(*m_engine, globalObject, errorAttrName, &exception);
    859879    JSStringRelease(errorAttrName);
    860     return JSValueIsInstanceOfConstructor(*m_engine, *this, JSValueToObject(*m_engine, error, /* exception */ 0), /* exception */ 0);
     880    bool result = JSValueIsInstanceOfConstructor(*m_engine, *this, JSValueToObject(*m_engine, error, &exception), &exception);
     881    m_engine->setException(exception);
     882    return result;
    861883}
    862884
  • trunk/JavaScriptCore/qt/tests/qscriptengine/tst_qscriptengine.cpp

    r62078 r62377  
    4949    void toObjectTwoEngines();
    5050    void newArray();
     51    void uncaughtException();
    5152};
    5253
     
    433434}
    434435
     436void tst_QScriptEngine::uncaughtException()
     437{
     438    QScriptEngine eng;
     439    QScriptValue fun = eng.evaluate("(function foo () { return null; });");
     440    QVERIFY(!eng.uncaughtException().isValid());
     441    QVERIFY(fun.isFunction());
     442    QScriptValue throwFun = eng.evaluate("( function() { throw new Error('Pong'); });");
     443    QVERIFY(throwFun.isFunction());
     444    {
     445        eng.evaluate("a = 10");
     446        QVERIFY(!eng.hasUncaughtException());
     447        QVERIFY(!eng.uncaughtException().isValid());
     448    }
     449    {
     450        eng.evaluate("1 = 2");
     451        QVERIFY(eng.hasUncaughtException());
     452        eng.clearExceptions();
     453        QVERIFY(!eng.hasUncaughtException());
     454    }
     455    {
     456        // Check if the call or toString functions can remove the last exception.
     457        QVERIFY(throwFun.call().isError());
     458        QVERIFY(eng.hasUncaughtException());
     459        QScriptValue exception = eng.uncaughtException();
     460        fun.call();
     461        exception.toString();
     462        QVERIFY(eng.hasUncaughtException());
     463        QVERIFY(eng.uncaughtException().strictlyEquals(exception));
     464    }
     465    eng.clearExceptions();
     466    {
     467        // Check if in the call function a new exception can override an existing one.
     468        throwFun.call();
     469        QVERIFY(eng.hasUncaughtException());
     470        QScriptValue exception = eng.uncaughtException();
     471        throwFun.call();
     472        QVERIFY(eng.hasUncaughtException());
     473        QVERIFY(!exception.strictlyEquals(eng.uncaughtException()));
     474    }
     475    {
     476        eng.evaluate("throwFun = (function foo () { throw new Error('bla') });");
     477        eng.evaluate("1;\nthrowFun();");
     478        QVERIFY(eng.hasUncaughtException());
     479        QCOMPARE(eng.uncaughtExceptionLineNumber(), 1);
     480        eng.clearExceptions();
     481        QVERIFY(!eng.hasUncaughtException());
     482    }
     483    for (int x = 1; x < 4; ++x) {
     484        QScriptValue ret = eng.evaluate("a = 10;\nb = 20;\n0 = 0;\n",
     485                                        QString::fromLatin1("FooScript") + QString::number(x),
     486                                        /* lineNumber */ x);
     487        QVERIFY(eng.hasUncaughtException());
     488        QCOMPARE(eng.uncaughtExceptionLineNumber(), x + 2);
     489        QVERIFY(eng.uncaughtException().strictlyEquals(ret));
     490        QVERIFY(eng.hasUncaughtException());
     491        QVERIFY(eng.uncaughtException().strictlyEquals(ret));
     492        QString backtrace = QString::fromLatin1("<anonymous>()@FooScript") + QString::number(x) + ":" + QString::number(x + 2);
     493        QCOMPARE(eng.uncaughtExceptionBacktrace().join(""), backtrace);
     494        QVERIFY(fun.call().isNull());
     495        QVERIFY(eng.hasUncaughtException());
     496        QCOMPARE(eng.uncaughtExceptionLineNumber(), x + 2);
     497        QVERIFY(eng.uncaughtException().strictlyEquals(ret));
     498        eng.clearExceptions();
     499        QVERIFY(!eng.hasUncaughtException());
     500        QCOMPARE(eng.uncaughtExceptionLineNumber(), -1);
     501        QVERIFY(!eng.uncaughtException().isValid());
     502        eng.evaluate("2 = 3");
     503        QVERIFY(eng.hasUncaughtException());
     504        QScriptValue ret2 = throwFun.call();
     505        QVERIFY(ret2.isError());
     506        QVERIFY(eng.hasUncaughtException());
     507        QVERIFY(eng.uncaughtException().strictlyEquals(ret2));
     508        QCOMPARE(eng.uncaughtExceptionLineNumber(), 1);
     509        eng.clearExceptions();
     510        QVERIFY(!eng.hasUncaughtException());
     511        eng.evaluate("1 + 2");
     512        QVERIFY(!eng.hasUncaughtException());
     513    }
     514}
     515
    435516QTEST_MAIN(tst_QScriptEngine)
    436517#include "tst_qscriptengine.moc"
Note: See TracChangeset for help on using the changeset viewer.