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>
This commit is contained in:
Lars Knoll 2013-11-11 11:22:24 +01:00 committed by The Qt Project
parent a5d0ed01c8
commit 965878e88a
7 changed files with 99 additions and 3 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -313,6 +313,7 @@ private slots:
void singletonFromQMLToCpp();
void setPropertyOnInvalid();
void miscTypeTest();
void stackLimits();
private:
// static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
@ -7404,7 +7405,12 @@ void tst_qqmlecmascript::miscTypeTest()
QVERIFY(q.toBool() == true);
delete object;
}
void tst_qqmlecmascript::stackLimits()
{
QJSEngine engine;
engine.evaluate(QStringLiteral("function foo() {foo();} try {foo()} catch(e) { }"));
}
QTEST_MAIN(tst_qqmlecmascript)