aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErik Verbruggen <erik.verbruggen@theqtcompany.com>2016-04-25 15:01:04 +0200
committerErik Verbruggen <erik.verbruggen@qt.io>2016-05-11 11:11:34 +0000
commit74f75a3a120b07bbfe6904512b338db8850874e4 (patch)
treeb3c1047484cb49d98947056a2d8662e7542d2463
parent78ad5069d4925445a9d39579c1f73c7911fc3582 (diff)
V4: Limit call depth by count, not by checking the native stack.
Getting the native stack size can be really expensive. For example, on Linux/x86_64 (Ubuntu 15.04), it is at least 200,000 instructions for a single-threaded application. With more threads (like qmlscene) it typically ends up around 1M(!) instructions. Worse, it is called twice in the ExecutionEngine constructor. So, now we limit the depth of JavaScript calls to a fixed number, 1234 by default. This can be changed by setting the environment variable QV4_MAX_CALL_DEPTH to the desired depth. Change-Id: Ic13c8efb2769e64fbc73deee6f6fa39d7c0b7af5 Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
-rw-r--r--src/qml/jsruntime/qv4engine.cpp109
-rw-r--r--src/qml/jsruntime/qv4engine_p.h33
2 files changed, 36 insertions, 106 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp
index 2560f065cf..49b6dce697 100644
--- a/src/qml/jsruntime/qv4engine.cpp
+++ b/src/qml/jsruntime/qv4engine.cpp
@@ -105,84 +105,6 @@ static ReturnedValue throwTypeError(CallContext *ctx)
return ctx->engine()->throwTypeError();
}
-const int MinimumStackSize = 256; // in kbytes
-
-QT_WARNING_PUSH
-QT_WARNING_DISABLE_MSVC(4172) // MSVC 2015: warning C4172: returning address of local variable or temporary: dummy
-
-quintptr getStackLimit()
-{
- quintptr stackLimit;
-#if USE(PTHREADS) && !OS(QNX)
-# if OS(DARWIN)
- pthread_t thread_self = pthread_self();
- void *stackTop = pthread_get_stackaddr_np(thread_self);
- stackLimit = reinterpret_cast<quintptr>(stackTop);
- quintptr size = 0;
- if (pthread_main_np()) {
- rlimit limit;
- getrlimit(RLIMIT_STACK, &limit);
- size = limit.rlim_cur;
- } else
- size = pthread_get_stacksize_np(thread_self);
- stackLimit -= size;
-# elif defined(__hppa)
- // On some architectures the stack grows upwards. All of these are rather exotic, so simply assume
- // everything is fine there.
- // Known examples:
- // -HP PA-RISC
- stackLimit = 0;
-
-# else
- pthread_attr_t attr;
-#if HAVE(PTHREAD_NP_H) && OS(FREEBSD)
- // on FreeBSD pthread_attr_init() must be called otherwise getting the attrs crashes
- if (pthread_attr_init(&attr) == 0 && pthread_attr_get_np(pthread_self(), &attr) == 0) {
-#else
- if (pthread_getattr_np(pthread_self(), &attr) == 0) {
-#endif
- void *stackBottom = Q_NULLPTR;
- size_t stackSize = 0;
-
- pthread_attr_getstack(&attr, &stackBottom, &stackSize);
- pthread_attr_destroy(&attr);
-
-# if defined(Q_OS_ANDROID)
- // Bionic pretends that the main thread has a tiny stack; work around it
- if (gettid() == getpid()) {
- rlimit limit;
- getrlimit(RLIMIT_STACK, &limit);
- stackBottom = reinterpret_cast<void*>(reinterpret_cast<quintptr>(stackBottom) + stackSize - limit.rlim_cur);
- }
-# endif
-
- stackLimit = reinterpret_cast<quintptr>(stackBottom);
- } else {
- int dummy;
- // this is inexact, as part of the stack is used when being called here,
- // but let's simply default to 1MB from where the stack is right now
- stackLimit = reinterpret_cast<qintptr>(&dummy) - 1024*1024;
- }
-
-# endif
-// This is wrong. StackLimit is the currently committed stack size, not the real end.
-// only way to get that limit is apparently by using VirtualQuery (Yuck)
-//#elif OS(WINDOWS)
-// PNT_TIB tib = (PNT_TIB)NtCurrentTeb();
-// stackLimit = static_cast<quintptr>(tib->StackLimit);
-#else
- int dummy;
- // this is inexact, as part of the stack is used when being called here,
- // but let's simply default to 1MB from where the stack is right now
- // (Note: triggers warning C4172 as of MSVC 2015, returning address of local variable)
- stackLimit = reinterpret_cast<qintptr>(&dummy) - 1024*1024;
-#endif
-
- // 256k slack
- return stackLimit + MinimumStackSize*1024;
-}
-
-QT_WARNING_POP
QJSEngine *ExecutionEngine::jsEngine() const
{
@@ -194,9 +116,12 @@ QQmlEngine *ExecutionEngine::qmlEngine() const
return v8Engine->engine();
}
+qint32 ExecutionEngine::maxCallDepth = -1;
+
ExecutionEngine::ExecutionEngine(EvalISelFactory *factory)
: current(0)
, hasException(false)
+ , callDepth(0)
, memoryManager(new QV4::MemoryManager(this))
, executableAllocator(new QV4::ExecutableAllocator)
, regExpAllocator(new QV4::ExecutableAllocator)
@@ -213,6 +138,15 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory)
, regExpCache(0)
, m_multiplyWrappedQObjects(0)
{
+ if (maxCallDepth == -1) {
+ bool ok = false;
+ maxCallDepth = qEnvironmentVariableIntValue("QV4_MAX_CALL_DEPTH", &ok);
+ if (!ok || maxCallDepth <= 0) {
+ maxCallDepth = 1234;
+ }
+ }
+ Q_ASSERT(maxCallDepth > 0);
+
MemoryManager::GCBlocker gcBlocker(memoryManager);
if (!factory) {
@@ -251,9 +185,6 @@ ExecutionEngine::ExecutionEngine(EvalISelFactory *factory)
// set up stack limits
jsStackLimit = jsStackBase + JSStackLimit/sizeof(Value);
- cStackLimit = getStackLimit();
- if (!recheckCStackLimits())
- qFatal("Fatal: Not enough stack space available for QML. Please increase the process stack size to more than %d KBytes.", MinimumStackSize);
identifierTable = new IdentifierTable(this);
@@ -1117,22 +1048,6 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError()
return error;
}
-bool ExecutionEngine::recheckCStackLimits()
-{
- int dummy;
-#ifdef Q_OS_WIN
- // ### this is only required on windows, where we currently use heuristics to get the stack limit
- if (cStackLimit - reinterpret_cast<quintptr>(&dummy) > 128*1024)
- // we're more then 128k away from our stack limit, assume the thread has changed, and
- // call getStackLimit
-#endif
- // this can happen after a thread change
- cStackLimit = getStackLimit();
-
- return (reinterpret_cast<quintptr>(&dummy) >= cStackLimit);
-}
-
-
// Variant conversion code
typedef QSet<QV4::Heap::Object *> V4ObjectSet;
diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h
index 4640f3f4cc..7fc880023f 100644
--- a/src/qml/jsruntime/qv4engine_p.h
+++ b/src/qml/jsruntime/qv4engine_p.h
@@ -75,15 +75,11 @@ namespace CompiledData {
struct CompilationUnit;
}
-#define CHECK_STACK_LIMITS(v4) \
- if ((v4->jsStackTop <= v4->jsStackLimit) && (reinterpret_cast<quintptr>(&v4) >= v4->cStackLimit || v4->recheckCStackLimits())) {} \
- else \
- return v4->throwRangeError(QStringLiteral("Maximum call stack size exceeded."))
-
-
struct Q_QML_EXPORT ExecutionEngine
{
private:
+ static qint32 maxCallDepth;
+
friend struct ExecutionContextSaver;
friend struct ExecutionContext;
friend struct Heap::ExecutionContext;
@@ -92,6 +88,7 @@ public:
Value *jsStackTop;
quint32 hasException;
+ qint32 callDepth;
MemoryManager *memoryManager;
ExecutableAllocator *executableAllocator;
@@ -101,7 +98,6 @@ public:
ExecutionContext *currentContext;
Value *jsStackLimit;
- quintptr cStackLimit;
WTF::BumpPointerAllocator *bumperPointerAllocator; // Used by Yarr Regex engine.
@@ -431,8 +427,6 @@ public:
InternalClass *newClass(const InternalClass &other);
- bool recheckCStackLimits();
-
// Exception handling
Value *exceptionValue;
StackTrace exceptionStackTrace;
@@ -465,6 +459,8 @@ public:
QV4::ReturnedValue metaTypeToJS(int type, const void *data);
void assertObjectBelongsToEngine(const Heap::Base &baseObject);
+
+ bool checkStackLimits(ReturnedValue &exception);
};
inline void ExecutionEngine::pushContext(Heap::ExecutionContext *context)
@@ -517,7 +513,26 @@ inline void Value::mark(ExecutionEngine *e)
o->mark(e);
}
+#define CHECK_STACK_LIMITS(v4) { ReturnedValue e; if ((v4)->checkStackLimits(e)) return e; } \
+ ExecutionEngineCallDepthRecorder _executionEngineCallDepthRecorder(v4);
+
+struct ExecutionEngineCallDepthRecorder
+{
+ ExecutionEngine *ee;
+ ExecutionEngineCallDepthRecorder(ExecutionEngine *e): ee(e) { ++ee->callDepth; }
+ ~ExecutionEngineCallDepthRecorder() { --ee->callDepth; }
+};
+
+inline bool ExecutionEngine::checkStackLimits(ReturnedValue &exception)
+{
+ if (Q_UNLIKELY((jsStackTop > jsStackLimit) || (callDepth >= maxCallDepth))) {
+ exception = throwRangeError(QStringLiteral("Maximum call stack size exceeded."));
+ return true;
+ }
+
+ return false;
+}
} // namespace QV4