diff options
author | Ulf Hermann <[email protected]> | 2022-10-10 12:48:49 +0200 |
---|---|---|
committer | Sami Shalayel <[email protected]> | 2022-12-09 20:04:56 +0100 |
commit | 4677b2bdd68b71c66d080652dd01e1f49b40f581 (patch) | |
tree | da7580e04f943e65fb7ee0e0594f3c66efcbacf7 /src/qml/jsruntime/qv4engine_p.h | |
parent | 8333954f7e3cccf4994c4e9996efac71db497acb (diff) |
QML: Add an accurate stack bounds checker
This re-introduces a stack bounds checker. The previous stack bounds
checker was removed in commit 74f75a3a120b07bbfe6904512b338db8850874e4
because the cost of determining the stack base was deemed too high.
Indeed, determining the stack base on linux using the pthread functions
costs about 200.000 instructions and the cost grows with the number of
concurrently running threads.
However, by reading /proc/self/maps directly we can trim this to about
125k instructions. Furthermore, with the new implementation we only need
to do this once per engine. Calling JavaScript functions of the same
engine from different threads is not supported. So we don't have to
consider the case of checking the bounds of a different thread than the
one the engine was created in. Furthermore, we get a more accurate
number now, which means we don't have to re-check when we get near the
boundary.
Also, change QV4::markChildQObjectsRecursively() to use an actual
QQueue instead of being recursive. This avoids the stack from overflowing when the stack is already almost full, and was leading to
crashes in the stackOverflow tests.
Make the stack smaller for the the tst_qquickloader::stackOverflow{,2} tests to run faster in the CI (and avoid the timeout).
Task-number: QTBUG-106875
Fixes: QTBUG-108182
Change-Id: Ia5d13caa7d072526ff2a3e1713ec7781afc154a9
Reviewed-by: Fabian Kosmale <[email protected]>
Diffstat (limited to 'src/qml/jsruntime/qv4engine_p.h')
-rw-r--r-- | src/qml/jsruntime/qv4engine_p.h | 64 |
1 files changed, 59 insertions, 5 deletions
diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 5434f55548..6fcf5e1b04 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -24,10 +24,12 @@ #include <QtCore/qelapsedtimer.h> #include <QtCore/qmutex.h> #include <QtCore/qset.h> +#include <QtCore/qprocessordetection.h> #include "qv4function_p.h" #include <private/qv4compileddata_p.h> #include <private/qv4executablecompilationunit_p.h> +#include <private/qv4stacklimits_p.h> namespace WTF { class BumpPointerAllocator; @@ -744,12 +746,50 @@ public: QMetaType type, const void *ptr, Heap::Object *parent = nullptr, int property = -1, uint flags = 0); + static void setMaxCallDepth(int maxCallDepth) { s_maxCallDepth = maxCallDepth; } + static int maxCallDepth() { return s_maxCallDepth; } + private: template<int Frames> friend struct ExecutionEngineCallDepthRecorder; static void initializeStaticMembers(); + bool inStack(const void *current) const + { +#if Q_STACK_GROWTH_DIRECTION > 0 + return current < cppStackLimit && current >= cppStackBase; +#else + return current > cppStackLimit && current <= cppStackBase; +#endif + } + + bool hasCppStackOverflow() + { + if (s_maxCallDepth >= 0) + return callDepth >= s_maxCallDepth; + + if (inStack(currentStackPointer())) + return false; + + // Double check the stack limits on failure. + // We may have moved to a different thread. + const StackProperties stack = stackProperties(); + cppStackBase = stack.base; + cppStackLimit = stack.softLimit; + return !inStack(currentStackPointer()); + } + + bool hasJsStackOverflow() const + { + return jsStackTop > jsStackLimit; + } + + bool hasStackOverflow() + { + return hasJsStackOverflow() || hasCppStackOverflow(); + } + static int s_maxCallDepth; static int s_jitCallCountThreshold; static int s_maxJSStackSize; @@ -789,7 +829,9 @@ private: QHash<QUrl, Value *> nativeModules; }; -#define CHECK_STACK_LIMITS(v4) if ((v4)->checkStackLimits()) return Encode::undefined(); \ +#define CHECK_STACK_LIMITS(v4) \ + if (v4->checkStackLimits()) \ + return Encode::undefined(); \ ExecutionEngineCallDepthRecorder _executionEngineCallDepthRecorder(v4); template<int Frames = 1> @@ -797,15 +839,27 @@ struct ExecutionEngineCallDepthRecorder { ExecutionEngine *ee; - ExecutionEngineCallDepthRecorder(ExecutionEngine *e): ee(e) { ee->callDepth += Frames; } - ~ExecutionEngineCallDepthRecorder() { ee->callDepth -= Frames; } + ExecutionEngineCallDepthRecorder(ExecutionEngine *e): ee(e) + { + if (ExecutionEngine::s_maxCallDepth >= 0) + ee->callDepth += Frames; + } + + ~ExecutionEngineCallDepthRecorder() + { + if (ExecutionEngine::s_maxCallDepth >= 0) + ee->callDepth -= Frames; + } - bool hasOverflow() const { return ee->callDepth >= ExecutionEngine::s_maxCallDepth; } + bool hasOverflow() const + { + return ee->hasCppStackOverflow(); + } }; inline bool ExecutionEngine::checkStackLimits() { - if (Q_UNLIKELY((jsStackTop > jsStackLimit) || (callDepth >= s_maxCallDepth))) { + if (Q_UNLIKELY(hasStackOverflow())) { throwRangeError(QStringLiteral("Maximum call stack size exceeded.")); return true; } |