summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/script/api/qscriptengine.cpp12
-rw-r--r--tests/auto/qscriptengine/tst_qscriptengine.cpp41
2 files changed, 51 insertions, 2 deletions
diff --git a/src/script/api/qscriptengine.cpp b/src/script/api/qscriptengine.cpp
index d96d11c..8186dec 100644
--- a/src/script/api/qscriptengine.cpp
+++ b/src/script/api/qscriptengine.cpp
@@ -1434,12 +1434,20 @@ bool QScriptEnginePrivate::isLikelyStackOverflowError(JSC::ExecState *exec, JSC:
/*!
\internal
- Called by the VM when an uncaught exception is detected.
- At the time of this call, the VM stack has not yet been unwound.
+ Called by the VM when an uncaught exception is being processed.
+ If the VM call stack contains a native call inbetween two JS calls at the
+ time the exception is thrown, this function will get called multiple times
+ for a single exception (once per "interval" of JS call frames). In other
+ words, at the time of this call, the VM stack can be in a partially unwound
+ state.
*/
void QScriptEnginePrivate::uncaughtException(JSC::ExecState *exec, unsigned bytecodeOffset,
JSC::JSValue value)
{
+ // Don't capture exception information if we already have.
+ if (uncaughtExceptionLineNumber != -1)
+ return;
+
QScript::SaveFrameHelper saveFrame(this, exec);
uncaughtExceptionLineNumber = exec->codeBlock()->lineNumberForBytecodeOffset(exec, bytecodeOffset);
diff --git a/tests/auto/qscriptengine/tst_qscriptengine.cpp b/tests/auto/qscriptengine/tst_qscriptengine.cpp
index 6d7540f..6d77b32 100644
--- a/tests/auto/qscriptengine/tst_qscriptengine.cpp
+++ b/tests/auto/qscriptengine/tst_qscriptengine.cpp
@@ -146,6 +146,8 @@ private slots:
void throwErrorFromProcessEvents();
void disableProcessEventsInterval();
void stacktrace();
+ void stacktrace_callJSFromCpp_data();
+ void stacktrace_callJSFromCpp();
void numberParsing_data();
void numberParsing();
void automaticSemicolonInsertion();
@@ -3107,6 +3109,45 @@ void tst_QScriptEngine::stacktrace()
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");