diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2023-05-08 11:38:14 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2023-05-09 21:28:06 +0200 |
commit | 406a9e1301e2597962ef0564348304be67d2c316 (patch) | |
tree | 40a2cfb1581dc7faf017e5e5598241298f2bd003 | |
parent | 1b89c1edcae68351632c2755e5408410c2ff98e3 (diff) | |
download | qtdeclarative-406a9e1301e2597962ef0564348304be67d2c316.tar.gz |
QML: Encode "missing" line number as negated address of stack frame
This way we can identify which entry in a stack frame to amend when
processing an exception in generated code. However, negative line
numbers are also used to signal the position of "Ret" instructions.
Since you cannot throw an exception from a "Ret" instruction, those
cannot collide, but we cannot qAbs() the line number anymore when saving
it in the stack trace. We have to qAbs() it in all the places where it's
read.
Pick-to: 6.5
Fixes: QTBUG-112946
Change-Id: I24dc4008fb7eab38e4d24e70211c22e46f1b72a7
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp | 2 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 2 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 6 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4errorobject.cpp | 4 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4stackframe.cpp | 11 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4stackframe_p.h | 2 | ||||
-rw-r--r-- | src/qml/qml/qqml.cpp | 16 | ||||
-rw-r--r-- | src/qml/qml/qqmlbuiltinfunctions.cpp | 17 | ||||
-rw-r--r-- | src/qmltest/quicktestutil.cpp | 2 | ||||
-rw-r--r-- | tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp | 5 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/exceptionFromInner.qml | 10 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 16 |
13 files changed, 72 insertions, 22 deletions
diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp index 904749d7f6..6aa44dda0d 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -220,7 +220,7 @@ QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int frame[QLatin1String("debuggerFrame")] = false; frame[QLatin1String("func")] = stackFrame.function; frame[QLatin1String("script")] = stackFrame.source; - frame[QLatin1String("line")] = stackFrame.line - 1; + frame[QLatin1String("line")] = qAbs(stackFrame.line) - 1; if (stackFrame.column >= 0) frame[QLatin1String("column")] = stackFrame.column; diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index eda1c4c931..eca6562b90 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -530,7 +530,7 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in for (auto &&frame: trace) exceptionStackTrace->push_back(QString::fromLatin1("%1:%2:%3:%4").arg( frame.function, - QString::number(frame.line), + QString::number(qAbs(frame.line)), QString::number(frame.column), frame.source) ); diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index d1ddde7089..f1a2bc0947 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1234,7 +1234,7 @@ StackTrace ExecutionEngine::stackTrace(int frameLimit) const QV4::StackFrame frame; frame.source = f->source(); frame.function = f->function(); - frame.line = qAbs(f->lineNumber()); + frame.line = f->lineNumber(); frame.column = -1; stack.append(frame); if (f->isJSTypesFrame()) { @@ -1272,7 +1272,7 @@ static inline char *v4StackTrace(const ExecutionContext *context) const QString fileName = url.isLocalFile() ? url.toLocalFile() : url.toString(); str << "frame={level=\"" << i << "\",func=\"" << stackTrace.at(i).function << "\",file=\"" << fileName << "\",fullname=\"" << fileName - << "\",line=\"" << stackTrace.at(i).line << "\",language=\"js\"}"; + << "\",line=\"" << qAbs(stackTrace.at(i).line) << "\",language=\"js\"}"; } } str << ']'; @@ -1467,7 +1467,7 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError() if (!trace.isEmpty()) { QV4::StackFrame frame = trace.constFirst(); error.setUrl(QUrl(frame.source)); - error.setLine(frame.line); + error.setLine(qAbs(frame.line)); error.setColumn(frame.column); } QV4::Scoped<QV4::ErrorObject> errorObj(scope, exception); diff --git a/src/qml/jsruntime/qv4errorobject.cpp b/src/qml/jsruntime/qv4errorobject.cpp index 516c81864f..35b5952d38 100644 --- a/src/qml/jsruntime/qv4errorobject.cpp +++ b/src/qml/jsruntime/qv4errorobject.cpp @@ -56,7 +56,7 @@ void Heap::ErrorObject::init(const Value &message, ErrorType t) e->d()->stackTrace = new StackTrace(scope.engine->stackTrace()); if (!e->d()->stackTrace->isEmpty()) { setProperty(scope.engine, QV4::ErrorObject::Index_FileName, scope.engine->newString(e->d()->stackTrace->at(0).source)); - setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Value::fromInt32(e->d()->stackTrace->at(0).line)); + setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Value::fromInt32(qAbs(e->d()->stackTrace->at(0).line))); } if (!message.isUndefined()) @@ -84,7 +84,7 @@ void Heap::ErrorObject::init(const Value &message, const QString &fileName, int Q_ASSERT(!e->d()->stackTrace->isEmpty()); setProperty(scope.engine, QV4::ErrorObject::Index_FileName, scope.engine->newString(e->d()->stackTrace->at(0).source)); - setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Value::fromInt32(e->d()->stackTrace->at(0).line)); + setProperty(scope.engine, QV4::ErrorObject::Index_LineNumber, Value::fromInt32(qAbs(e->d()->stackTrace->at(0).line))); if (!message.isUndefined()) setProperty(scope.engine, QV4::ErrorObject::Index_Message, message); diff --git a/src/qml/jsruntime/qv4stackframe.cpp b/src/qml/jsruntime/qv4stackframe.cpp index e8ff9a89bc..5117e745a0 100644 --- a/src/qml/jsruntime/qv4stackframe.cpp +++ b/src/qml/jsruntime/qv4stackframe.cpp @@ -40,7 +40,7 @@ int CppStackFrame::lineNumber() const { if (auto *line = lineAndStatement(this)) return line->line; - return -1; + return missingLineNumber(); } int CppStackFrame::statementNumber() const @@ -50,6 +50,15 @@ int CppStackFrame::statementNumber() const return -1; } +int CppStackFrame::missingLineNumber() const +{ + // Remove the first bit so that we can cast to positive int and negate. + // Remove the last bit so that it can't be -1. + const int result = -int(quintptr(this) & 0x7ffffffe); + Q_ASSERT(result < -1); + return result; +} + ReturnedValue QV4::CppStackFrame::thisObject() const { if (isJSTypesFrame()) diff --git a/src/qml/jsruntime/qv4stackframe_p.h b/src/qml/jsruntime/qv4stackframe_p.h index 2777d79c31..9c3e3700ce 100644 --- a/src/qml/jsruntime/qv4stackframe_p.h +++ b/src/qml/jsruntime/qv4stackframe_p.h @@ -83,6 +83,8 @@ struct Q_QML_PRIVATE_EXPORT CppStackFrame : protected CppStackFrameBase int lineNumber() const; int statementNumber() const; + int missingLineNumber() const; + CppStackFrame *parentFrame() const { return parent; } void setParentFrame(CppStackFrame *parentFrame) { parent = parentFrame; } diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 4304934019..ca9c1d8e52 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -1234,13 +1234,25 @@ static bool initValueLookup(QV4::Lookup *l, QV4::ExecutableCompilationUnit *comp static void amendException(QV4::ExecutionEngine *engine) { + const int missingLineNumber = engine->currentStackFrame->missingLineNumber(); const int lineNumber = engine->currentStackFrame->lineNumber(); - engine->exceptionStackTrace.front().line = lineNumber; + Q_ASSERT(missingLineNumber != lineNumber); + + auto amendStackTrace = [&](QV4::StackTrace *stackTrace) { + for (auto it = stackTrace->begin(), end = stackTrace->end(); it != end; ++it) { + if (it->line == missingLineNumber) { + it->line = lineNumber; + break; + } + } + }; + + amendStackTrace(&engine->exceptionStackTrace); QV4::Scope scope(engine); QV4::Scoped<QV4::ErrorObject> error(scope, *engine->exceptionValue); if (error) // else some other value was thrown - error->d()->stackTrace->front().line = lineNumber; + amendStackTrace(error->d()->stackTrace); } diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp index 9d75073e84..ebb5c301db 100644 --- a/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -1755,15 +1755,14 @@ static QString jsStack(QV4::ExecutionEngine *engine) { const QV4::StackFrame &frame = stackTrace.at(i); QString stackFrame; - if (frame.column >= 0) - stackFrame = QStringLiteral("%1 (%2:%3:%4)").arg(frame.function, - frame.source, - QString::number(frame.line), - QString::number(frame.column)); - else - stackFrame = QStringLiteral("%1 (%2:%3)").arg(frame.function, - frame.source, - QString::number(frame.line)); + if (frame.column >= 0) { + stackFrame = QStringLiteral("%1 (%2:%3:%4)").arg( + frame.function, frame.source, + QString::number(qAbs(frame.line)), QString::number(frame.column)); + } else { + stackFrame = QStringLiteral("%1 (%2:%3)").arg( + frame.function, frame.source, QString::number(qAbs(frame.line))); + } if (i) stack += QLatin1Char('\n'); diff --git a/src/qmltest/quicktestutil.cpp b/src/qmltest/quicktestutil.cpp index 15f530c60e..c7cac4b4b2 100644 --- a/src/qmltest/quicktestutil.cpp +++ b/src/qmltest/quicktestutil.cpp @@ -87,7 +87,7 @@ int QuickTestUtil::callerLine(int frameIndex) const QVector<QV4::StackFrame> stack = v4->stackTrace(frameIndex + 2); if (stack.size() > frameIndex + 1) - return stack.at(frameIndex + 1).line; + return qAbs(stack.at(frameIndex + 1).line); return -1; } diff --git a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp index 6f147446f0..f95a7ade9a 100644 --- a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp +++ b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp @@ -274,9 +274,10 @@ public: void dumpStackTrace() const { qDebug() << "Stack depth:" << m_stackTrace.size(); - foreach (const QV4::StackFrame &frame, m_stackTrace) + for (const QV4::StackFrame &frame : m_stackTrace) { qDebug("\t%s (%s:%d:%d)", qPrintable(frame.function), qPrintable(frame.source), - frame.line, frame.column); + qAbs(frame.line), frame.column); + } } }; diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 370ef4ac9c..363c24c648 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -105,6 +105,7 @@ set(qml_files equalityQUrl.qml equalityVarAndNonStorable.qml equalsUndefined.qml + exceptionFromInner.qml excessiveParameters.qml extendedTypes.qml failures.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/exceptionFromInner.qml b/tests/auto/qml/qmlcppcodegen/data/exceptionFromInner.qml new file mode 100644 index 0000000000..13855356f2 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/exceptionFromInner.qml @@ -0,0 +1,10 @@ +pragma Strict +import QtQml + +QtObject { + property QtObject theNull: null + + function doFail() : string { return theNull.objectName } + function delegateFail() : string { doFail() } + function disbelieveFail() : string { delegateFail() } +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 20e6922dc4..f3869e6e40 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -74,6 +74,7 @@ private slots: void equalityVarAndNonStorable(); void equalsUndefined(); void evadingAmbiguity(); + void exceptionFromInner(); void excessiveParameters(); void extendedTypes(); void failures(); @@ -1415,6 +1416,21 @@ void tst_QmlCppCodegen::evadingAmbiguity() QCOMPARE(o2->property("i").toString(), QStringLiteral("Ambiguous2")); } +void tst_QmlCppCodegen::exceptionFromInner() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/exceptionFromInner.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QTest::ignoreMessage( + QtWarningMsg, + "qrc:/qt/qml/TestTypes/exceptionFromInner.qml:7: TypeError: " + "Cannot read property 'objectName' of null"); + QMetaObject::invokeMethod(object.data(), "disbelieveFail"); +} + void tst_QmlCppCodegen::excessiveParameters() { QQmlEngine engine; |