aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime/qv4engine_p.h
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-10-10 12:48:49 +0200
committerSami Shalayel <sami.shalayel@qt.io>2022-12-09 20:04:56 +0100
commit4677b2bdd68b71c66d080652dd01e1f49b40f581 (patch)
treeda7580e04f943e65fb7ee0e0594f3c66efcbacf7 /src/qml/jsruntime/qv4engine_p.h
parent8333954f7e3cccf4994c4e9996efac71db497acb (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 <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qml/jsruntime/qv4engine_p.h')
-rw-r--r--src/qml/jsruntime/qv4engine_p.h64
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;
}