diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-11-08 08:20:04 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-11-11 17:33:12 +0100 |
commit | edc01fbfa430d6f0ce66f1871ab28e0f691ee252 (patch) | |
tree | c703a04ce0c8b067b440e938dfc76c99cf9867e8 | |
parent | ad31adffbd5c6d59c02454f17ff0a20bfd56e22c (diff) |
QML: Check for stack overflows when creating objects
Pick-to: 5.15 6.2 6.4
Fixes: QTBUG-106875
Change-Id: I3b0abda6948b79a9e3cf263f27885037fff1804c
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
-rw-r--r-- | src/qml/jsruntime/qv4engine_p.h | 10 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4jsonobject.cpp | 2 | ||||
-rw-r--r-- | src/qml/qml/qqmlincubator.cpp | 15 | ||||
-rw-r--r-- | tests/auto/quick/qquickloader/data/overflow.qml | 5 | ||||
-rw-r--r-- | tests/auto/quick/qquickloader/tst_qquickloader.cpp | 13 |
5 files changed, 42 insertions, 3 deletions
diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index d1633abf52..067ddc7243 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -741,6 +741,9 @@ public: QV4::ExecutionContext *ctxt, int argc, const QV4::Value *argv); private: + template<int Frames> + friend struct ExecutionEngineCallDepthRecorder; + QV4::ReturnedValue fromData( QMetaType type, const void *ptr, Heap::Object *parent = nullptr, int property = -1, uint flags = 0); @@ -788,12 +791,15 @@ private: #define CHECK_STACK_LIMITS(v4) if ((v4)->checkStackLimits()) return Encode::undefined(); \ ExecutionEngineCallDepthRecorder _executionEngineCallDepthRecorder(v4); +template<int Frames = 1> struct ExecutionEngineCallDepthRecorder { ExecutionEngine *ee; - ExecutionEngineCallDepthRecorder(ExecutionEngine *e): ee(e) { ++ee->callDepth; } - ~ExecutionEngineCallDepthRecorder() { --ee->callDepth; } + ExecutionEngineCallDepthRecorder(ExecutionEngine *e): ee(e) { ee->callDepth += Frames; } + ~ExecutionEngineCallDepthRecorder() { ee->callDepth -= Frames; } + + bool hasOverflow() const { return ee->callDepth >= ExecutionEngine::s_maxCallDepth; } }; inline bool ExecutionEngine::checkStackLimits() diff --git a/src/qml/jsruntime/qv4jsonobject.cpp b/src/qml/jsruntime/qv4jsonobject.cpp index 6fc854665c..4f46e605af 100644 --- a/src/qml/jsruntime/qv4jsonobject.cpp +++ b/src/qml/jsruntime/qv4jsonobject.cpp @@ -628,7 +628,7 @@ public: bool foundProblem() const { return m_callDepthRecorder.ee->hasException; } private: - ExecutionEngineCallDepthRecorder m_callDepthRecorder; + ExecutionEngineCallDepthRecorder<1> m_callDepthRecorder; }; static QString quote(const QString &str) diff --git a/src/qml/qml/qqmlincubator.cpp b/src/qml/qml/qqmlincubator.cpp index 3c0a9dff36..6ee9e7e2ff 100644 --- a/src/qml/qml/qqmlincubator.cpp +++ b/src/qml/qml/qqmlincubator.cpp @@ -232,6 +232,7 @@ void QQmlIncubatorPrivate::forceCompletion(QQmlInstantiationInterrupt &i) } } + void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) { if (!compilationUnit) @@ -243,6 +244,20 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) // get a copy of the engine pointer as it might get reset; QQmlEnginePrivate *enginePriv = this->enginePriv; + // Incubating objects takes quite a bit more stack space than our usual V4 function + enum { EstimatedSizeInV4Frames = 2 }; + QV4::ExecutionEngineCallDepthRecorder<EstimatedSizeInV4Frames> callDepthRecorder( + compilationUnit->engine); + if (callDepthRecorder.hasOverflow()) { + QQmlError error; + error.setMessageType(QtCriticalMsg); + error.setUrl(compilationUnit->url()); + error.setDescription(QQmlComponent::tr("Maximum call stack size exceeded.")); + errors << error; + progress = QQmlIncubatorPrivate::Completed; + goto finishIncubate; + } + if (!vmeGuard.isOK()) { QQmlError error; error.setMessageType(QtInfoMsg); diff --git a/tests/auto/quick/qquickloader/data/overflow.qml b/tests/auto/quick/qquickloader/data/overflow.qml new file mode 100644 index 0000000000..e5fdfee182 --- /dev/null +++ b/tests/auto/quick/qquickloader/data/overflow.qml @@ -0,0 +1,5 @@ +import QtQuick + +Loader { + source: "overflow.qml" +} diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index 5b63841259..dcaba81c60 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -110,6 +110,8 @@ private slots: void setSourceAndCheckStatus(); void asyncLoaderRace(); void noEngine(); + + void stackOverflow(); }; Q_DECLARE_METATYPE(QList<QQmlError>) @@ -1523,6 +1525,17 @@ void tst_QQuickLoader::noEngine() QTRY_COMPARE(o->property("changes").toInt(), 1); } +void tst_QQuickLoader::stackOverflow() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("overflow.qml"); + QQmlComponent component(&engine, url); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + const QString message = url.toString() + QStringLiteral(": Maximum call stack size exceeded."); + QTest::ignoreMessage(QtCriticalMsg, qPrintable(message)); + QScopedPointer<QObject> o(component.create()); +} + QTEST_MAIN(tst_QQuickLoader) #include "tst_qquickloader.moc" |