aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@digia.com>2013-11-11 11:22:24 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-11-18 11:05:25 +0100
commit965878e88a9211b6995d57fddccf22ed365a9772 (patch)
tree9a1420b21c307e263c6f5ee269d08c8f14fdcdec /src
parenta5d0ed01c8cbda9d6ec7e1a30b3f1266d3a277f8 (diff)
Limit the amount of memory we allocate on the stack
Setup limits for both the C and the JS stack, and check them before entering functions. If we run out of space, throw a RangeError exception. Be careful and recheck the stack bounds when things go outside. This catches the case where the engine got moved to another thread changing the stack boundaries. Windows currently uses an unsafe fallback implementation, this needs to be fixed later on. Task-number: QTBUG-34568 Change-Id: I22fbcbec57b28f9cc8a49e12f1cc6e53e4f07888 Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com> Reviewed-by: Gunnar Sletta <gunnar.sletta@digia.com>
Diffstat (limited to 'src')
-rw-r--r--src/qml/jsruntime/qv4context.cpp7
-rw-r--r--src/qml/jsruntime/qv4context_p.h1
-rw-r--r--src/qml/jsruntime/qv4engine.cpp65
-rw-r--r--src/qml/jsruntime/qv4engine_p.h14
-rw-r--r--src/qml/jsruntime/qv4functionobject.cpp7
-rw-r--r--src/qml/jsruntime/qv4script.cpp2
6 files changed, 93 insertions, 3 deletions
diff --git a/src/qml/jsruntime/qv4context.cpp b/src/qml/jsruntime/qv4context.cpp
index 18b0de3077..97247ad368 100644
--- a/src/qml/jsruntime/qv4context.cpp
+++ b/src/qml/jsruntime/qv4context.cpp
@@ -579,6 +579,13 @@ ReturnedValue ExecutionContext::throwRangeError(const ValueRef value)
return throwError(error);
}
+ReturnedValue ExecutionContext::throwRangeError(const QString &message)
+{
+ Scope scope(this);
+ ScopedObject error(scope, engine->newRangeErrorObject(message));
+ return throwError(error);
+}
+
ReturnedValue ExecutionContext::throwURIError(const ValueRef msg)
{
Scope scope(this);
diff --git a/src/qml/jsruntime/qv4context_p.h b/src/qml/jsruntime/qv4context_p.h
index 9a7b9a61b2..ccb5cf98f8 100644
--- a/src/qml/jsruntime/qv4context_p.h
+++ b/src/qml/jsruntime/qv4context_p.h
@@ -136,6 +136,7 @@ struct Q_QML_EXPORT ExecutionContext
ReturnedValue throwReferenceError(const ValueRef value);
ReturnedValue throwReferenceError(const QString &value, const QString &fileName, int line, int column);
ReturnedValue throwRangeError(const ValueRef value);
+ ReturnedValue throwRangeError(const QString &message);
ReturnedValue throwURIError(const ValueRef msg);
ReturnedValue throwUnimplemented(const QString &message);
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp
index 7af313307e..7189e60cae 100644
--- a/src/qml/jsruntime/qv4engine.cpp
+++ b/src/qml/jsruntime/qv4engine.cpp
@@ -72,6 +72,10 @@
#include "qv4isel_moth_p.h"
+#if USE(PTHREADS)
+# include <pthread.h>
+#endif
+
QT_BEGIN_NAMESPACE
using namespace QV4;
@@ -83,6 +87,42 @@ static ReturnedValue throwTypeError(CallContext *ctx)
return ctx->throwTypeError();
}
+quintptr getStackLimit()
+{
+ quintptr stackLimit;
+#if USE(PTHREADS)
+# if OS(DARWIN)
+ pthread_t thread_self = pthread_self();
+ void *stackTop = pthread_get_stackaddr_np(thread_self);
+ stackLimit = reinterpret_cast<quintptr>(stackTop);
+ stackLimit -= pthread_get_stacksize_np(thread_self);
+# else
+ void* stackBottom = 0;
+ pthread_attr_t attr;
+ pthread_getattr_np(pthread_self(), &attr);
+ size_t stackSize = 0;
+ pthread_attr_getstack(&attr, &stackBottom, &stackSize);
+ pthread_attr_destroy(&attr);
+
+ stackLimit = reinterpret_cast<quintptr>(stackBottom);
+# 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
+ stackLimit = reinterpret_cast<qintptr>(&dummy) - 1024*1024;
+#endif
+
+ // 256k slack
+ return stackLimit + 256*1024;
+}
+
+
ExecutionEngine::ExecutionEngine(QQmlJS::EvalISelFactory *factory)
: memoryManager(new QV4::MemoryManager)
, executableAllocator(new QV4::ExecutableAllocator)
@@ -118,11 +158,17 @@ ExecutionEngine::ExecutionEngine(QQmlJS::EvalISelFactory *factory)
memoryManager->setExecutionEngine(this);
- // reserve 8MB for the JS stack
- *jsStack = WTF::PageAllocation::allocate(8*1024*1024, WTF::OSAllocator::JSVMStackPages, true);
+ // reserve space for the JS stack
+ // we allow it to grow to 2 times JSStackLimit, as we can overshoot due to garbage collection
+ // and ScopedValues allocated outside of JIT'ed methods.
+ *jsStack = WTF::PageAllocation::allocate(2*JSStackLimit, WTF::OSAllocator::JSVMStackPages, true);
jsStackBase = (SafeValue *)jsStack->base();
jsStackTop = jsStackBase;
+ // set up stack limits
+ jsStackLimit = jsStackBase + JSStackLimit/sizeof(SafeValue);
+ cStackLimit = getStackLimit();
+
Scope scope(this);
identifierTable = new IdentifierTable(this);
@@ -867,4 +913,19 @@ QQmlError ExecutionEngine::catchExceptionAsQmlError(ExecutionContext *context)
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);
+}
+
QT_END_NAMESPACE
diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h
index 2df148a4a8..b4972904ee 100644
--- a/src/qml/jsruntime/qv4engine_p.h
+++ b/src/qml/jsruntime/qv4engine_p.h
@@ -113,6 +113,12 @@ class RegExpCache;
struct QmlExtensions;
struct Exception;
+#define CHECK_STACK_LIMITS(v4) \
+ if ((v4->jsStackTop <= v4->jsStackLimit) && (reinterpret_cast<quintptr>(&v4) >= v4->cStackLimit || v4->recheckCStackLimits())) {} \
+ else \
+ return v4->current->throwRangeError(QStringLiteral("Maximum call stack size exceeded."))
+
+
struct Q_QML_EXPORT ExecutionEngine
{
MemoryManager *memoryManager;
@@ -123,11 +129,15 @@ struct Q_QML_EXPORT ExecutionEngine
ExecutionContext *current;
GlobalContext *rootContext;
+ SafeValue *jsStackTop;
+ SafeValue *jsStackLimit;
+ quintptr cStackLimit;
+
WTF::BumpPointerAllocator *bumperPointerAllocator; // Used by Yarr Regex engine.
+ enum { JSStackLimit = 4*1024*1024 };
WTF::PageAllocation *jsStack;
SafeValue *jsStackBase;
- SafeValue *jsStackTop;
SafeValue *stackPush(uint nValues) {
SafeValue *ptr = jsStackTop;
@@ -329,6 +339,8 @@ struct Q_QML_EXPORT ExecutionEngine
QmlExtensions *qmlExtensions();
+ bool recheckCStackLimits();
+
// Exception handling
SafeValue exceptionValue;
quint32 hasException;
diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp
index 7841f7c331..aa1cb89a44 100644
--- a/src/qml/jsruntime/qv4functionobject.cpp
+++ b/src/qml/jsruntime/qv4functionobject.cpp
@@ -438,6 +438,7 @@ ReturnedValue ScriptFunction::construct(Managed *that, CallData *callData)
ExecutionEngine *v4 = that->internalClass->engine;
if (v4->hasException)
return Encode::undefined();
+ CHECK_STACK_LIMITS(v4);
Scope scope(v4);
Scoped<ScriptFunction> f(scope, static_cast<ScriptFunction *>(that));
@@ -468,6 +469,7 @@ ReturnedValue ScriptFunction::call(Managed *that, CallData *callData)
ExecutionEngine *v4 = f->internalClass->engine;
if (v4->hasException)
return Encode::undefined();
+ CHECK_STACK_LIMITS(v4);
ExecutionContext *context = v4->current;
Scope scope(context);
@@ -523,6 +525,7 @@ ReturnedValue SimpleScriptFunction::construct(Managed *that, CallData *callData)
ExecutionEngine *v4 = that->internalClass->engine;
if (v4->hasException)
return Encode::undefined();
+ CHECK_STACK_LIMITS(v4);
Scope scope(v4);
Scoped<SimpleScriptFunction> f(scope, static_cast<SimpleScriptFunction *>(that));
@@ -566,6 +569,7 @@ ReturnedValue SimpleScriptFunction::call(Managed *that, CallData *callData)
ExecutionEngine *v4 = that->internalClass->engine;
if (v4->hasException)
return Encode::undefined();
+ CHECK_STACK_LIMITS(v4);
SimpleScriptFunction *f = static_cast<SimpleScriptFunction *>(that);
@@ -617,6 +621,7 @@ ReturnedValue BuiltinFunction::call(Managed *that, CallData *callData)
ExecutionEngine *v4 = f->internalClass->engine;
if (v4->hasException)
return Encode::undefined();
+ CHECK_STACK_LIMITS(v4);
ExecutionContext *context = v4->current;
@@ -636,6 +641,8 @@ ReturnedValue IndexedBuiltinFunction::call(Managed *that, CallData *callData)
ExecutionEngine *v4 = f->internalClass->engine;
if (v4->hasException)
return Encode::undefined();
+ CHECK_STACK_LIMITS(v4);
+
ExecutionContext *context = v4->current;
Scope scope(v4);
diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp
index 26cb0d940c..25791cff61 100644
--- a/src/qml/jsruntime/qv4script.cpp
+++ b/src/qml/jsruntime/qv4script.cpp
@@ -89,6 +89,8 @@ QmlBindingWrapper::QmlBindingWrapper(ExecutionContext *scope, ObjectRef qml)
ReturnedValue QmlBindingWrapper::call(Managed *that, CallData *)
{
ExecutionEngine *engine = that->engine();
+ CHECK_STACK_LIMITS(engine);
+
Scope scope(engine);
QmlBindingWrapper *This = static_cast<QmlBindingWrapper *>(that);
Q_ASSERT(This->function);