Changeset 161180 in webkit


Ignore:
Timestamp:
Dec 31, 2013, 2:26:49 AM (11 years ago)
Author:
mark.lam@apple.com
Message:

CStack: Need a separate stack limit for the JS stack and the C stack.
https://bugs.webkit.org/show_bug.cgi?id=126320.

Not yet reviewed.

With this patch, we now accurately track how much JS stack space the
VM has used, and cap that at the value specified by Options::maxStackSize().

The JS stack space is measured in chunks, one for each VMEntryScope. Each
VMEntryScope will also keep a running total of the sum of all the stack
usage of previous VMEntryScopes to simplify the calculation of the total
usage.

  • interpreter/Interpreter.cpp:

(JSC::Interpreter::execute):

  • In Interpreter::execute() for programs, we were installing the VMEntryScope even if we end up only processing a JSON object. This poses a problem because the code that processes the JSON object will call functions that will re-enter the VM thereby installing another VMEntryScope. Each VMEntryScope expects VM.topCallFrame and VM.stackPointerAtVMEntry to be set up properly for the previous VMEntryScope. However, in this case, we've installed the VMEntryScope for a potential ProgramExecutable but never executed a program to initialize its values for VM.topCallFrame and VM.stackPointerAtVMEntry. This in turn causes a crash when the second VMEntryScope makes use of VM.topCallFrame.

The fix is simply to defer installation of the VMEntryScope for the

program till we are actually sure that we will attempt to execute a
program.

  • llint/LLIntSlowPaths.cpp:

(JSC::LLInt::LLINT_SLOW_PATH_DECL(stack_check)):

  • In llint_stack_check(), we pop the frame that we failed to set up before throwing the StackOverflowError. Added an update of topCallFrame here to reflect this popping action.
  • llint/LowLevelInterpreter64.asm:
  • The VMEntryScope will have to calculate and set up a jsStackLimit value before we actually know what the stack pointer is at the point when we re-enter the VM. So, instead of using the real stackPointerAtVMEntry for the jsStackLimit calculation, we use an estimate.

doCallToJavaScript() here will set VM.stackPointerAtVMEntry to its

real value. But before it does that, it needs to adjust VM.jsStackLimit
by the delta between the estimate and the real stackPointerAtVMEntry.

  • If doCallToJavaScript() fails its stack check for incoming args, it also needs to clear VM.stackPointerAtVMEntry so that C code can assert that the various pointers into the stack remain consistent.

By consistent, I mean that, given that the stack grows down, and that
VM.stackPointerAtVMEntry and VM.topCallFrame are properly initialized,
then the following should always be true:

let topOfStack = VM.topCallFrame->topOfFrame();
stackPointerAtVMEntry >= topOfStack >= stack pointer in a C helper function

and

JSStack.containsAddress(stackPointerAtVMEntry)

and

JSStack.containsAddress(topOfStack)

Clearing VM.stackPointerAtVMEntry, if the stack check in doCallToJavaScript()
fails, indicates we failed to enter the current VMEntryScope and that we
should be using the previous VMEntryScope's stackPointerAtVMEntry and
topCallFrame for the above assertions.

NOTE: These assertions are done in VMEntryScope::updateStackLimits().

  • runtime/VM.cpp:

(JSC::VM::VM):

  • runtime/VM.h:
  • runtime/VMEntryScope.cpp:

(JSC::VMEntryScope::VMEntryScope):
(JSC::VMEntryScope::~VMEntryScope):
(JSC::VMEntryScope::stackUsageFor):
(JSC::VMEntryScope::updateStackLimits):
(JSC::VMEntryScope::currentStackPointer):
(JSC::VMEntryScope::requiredCapacity):

  • requiredCapacity() now computes the excess amount C stack capacity that we have available, and discount it in the computation of the JS stack limit. By excess, I mean the amount of unused C stack space that exceeds the amount of unused JS stack space capacity.
  • runtime/VMEntryScope.h:
Location:
branches/jsCStack/Source/JavaScriptCore
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • branches/jsCStack/Source/JavaScriptCore/ChangeLog

    r161175 r161180  
     12013-12-31  Mark Lam  <mark.lam@apple.com>
     2
     3        CStack: Need a separate stack limit for the JS stack and the C stack.
     4        https://bugs.webkit.org/show_bug.cgi?id=126320.
     5
     6        Not yet reviewed.
     7
     8        With this patch, we now accurately track how much JS stack space the
     9        VM has used, and cap that at the value specified by Options::maxStackSize().
     10
     11        The JS stack space is measured in chunks, one for each VMEntryScope. Each
     12        VMEntryScope will also keep a running total of the sum of all the stack
     13        usage of previous VMEntryScopes to simplify the calculation of the total
     14        usage.
     15
     16        * interpreter/Interpreter.cpp:
     17        (JSC::Interpreter::execute):
     18        - In Interpreter::execute() for programs, we were installing the VMEntryScope
     19          even if we end up only processing a JSON object. This poses a problem
     20          because the code that processes the JSON object will call functions that
     21          will re-enter the VM thereby installing another VMEntryScope. Each
     22          VMEntryScope expects VM.topCallFrame and VM.stackPointerAtVMEntry to be
     23          set up properly for the previous VMEntryScope. However, in this case,
     24          we've installed the VMEntryScope for a potential ProgramExecutable but
     25          never executed a program to initialize its values for VM.topCallFrame and
     26          VM.stackPointerAtVMEntry. This in turn causes a crash when the second
     27          VMEntryScope makes use of VM.topCallFrame.
     28              The fix is simply to defer installation of the VMEntryScope for the
     29          program till we are actually sure that we will attempt to execute a
     30          program.
     31
     32        * llint/LLIntSlowPaths.cpp:
     33        (JSC::LLInt::LLINT_SLOW_PATH_DECL(stack_check)):
     34        - In llint_stack_check(), we pop the frame that we failed to set up before
     35          throwing the StackOverflowError. Added an update of topCallFrame here to
     36          reflect this popping action.
     37
     38        * llint/LowLevelInterpreter64.asm:
     39        - The VMEntryScope will have to calculate and set up a jsStackLimit value
     40          before we actually know what the stack pointer is at the point when we
     41          re-enter the VM. So, instead of using the real stackPointerAtVMEntry for
     42          the jsStackLimit calculation, we use an estimate.
     43              doCallToJavaScript() here will set VM.stackPointerAtVMEntry to its
     44          real value. But before it does that, it needs to adjust VM.jsStackLimit
     45          by the delta between the estimate and the real stackPointerAtVMEntry.
     46
     47        - If doCallToJavaScript() fails its stack check for incoming args, it also
     48          needs to clear VM.stackPointerAtVMEntry so that C code can assert that
     49          the various pointers into the stack remain consistent.
     50
     51          By consistent, I mean that, given that the stack grows down, and that
     52          VM.stackPointerAtVMEntry and VM.topCallFrame are properly initialized,
     53          then the following should always be true:
     54
     55              let topOfStack = VM.topCallFrame->topOfFrame();
     56              stackPointerAtVMEntry >= topOfStack >= stack pointer in a C helper function
     57          and
     58              JSStack.containsAddress(stackPointerAtVMEntry)
     59          and
     60              JSStack.containsAddress(topOfStack)
     61
     62          Clearing VM.stackPointerAtVMEntry, if the stack check in doCallToJavaScript()
     63          fails, indicates we failed to enter the current VMEntryScope and that we
     64          should be using the previous VMEntryScope's stackPointerAtVMEntry and
     65          topCallFrame for the above assertions.
     66
     67          NOTE: These assertions are done in VMEntryScope::updateStackLimits().
     68
     69        * runtime/VM.cpp:
     70        (JSC::VM::VM):
     71        * runtime/VM.h:
     72        * runtime/VMEntryScope.cpp:
     73        (JSC::VMEntryScope::VMEntryScope):
     74        (JSC::VMEntryScope::~VMEntryScope):
     75        (JSC::VMEntryScope::stackUsageFor):
     76        (JSC::VMEntryScope::updateStackLimits):
     77        (JSC::VMEntryScope::currentStackPointer):
     78        (JSC::VMEntryScope::requiredCapacity):
     79        - requiredCapacity() now computes the excess amount C stack capacity that
     80          we have available, and discount it in the computation of the JS stack
     81          limit. By excess, I mean the amount of unused C stack space that
     82          exceeds the amount of unused JS stack space capacity.
     83
     84        * runtime/VMEntryScope.h:
     85
    1862013-12-30  Mark Lam  <mark.lam@apple.com>
    287
  • branches/jsCStack/Source/JavaScriptCore/interpreter/Interpreter.cpp

    r160967 r161180  
    771771        return jsNull();
    772772
    773     VMEntryScope entryScope(vm, scope->globalObject());
    774773    if (!vm.isSafeToRecurse())
    775774        return checkedReturn(throwStackOverflowError(callFrame));
     
    878877    // object.
    879878
     879    VMEntryScope entryScope(vm, scope->globalObject());
     880
    880881    // Compile source to bytecode if necessary:
    881882    if (JSObject* error = program->initializeGlobalProperties(vm, callFrame, scope))
  • branches/jsCStack/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp

    r161084 r161180  
    459459    // throw the StackOverflowError unconditionally.
    460460#if ENABLE(LLINT_C_LOOP)
    461     ASSERT(!exec->vm().interpreter->stack().containsAddress(exec->topOfFrame()));
     461    ASSERT(!vm.interpreter->stack().containsAddress(exec->topOfFrame()));
    462462    if (LIKELY(vm.interpreter->stack().ensureCapacityFor(exec->topOfFrame())))
    463463        LLINT_RETURN_TWO(pc, 0);
     
    465465
    466466    exec = exec->callerFrame();
     467    vm.topCallFrame = exec;
    467468    Interpreter::ErrorHandlingMode mode(exec);
    468469    CommonSlowPaths::interpreterThrowInCaller(exec, createStackOverflowError(exec));
  • branches/jsCStack/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm

    r160947 r161180  
    180180    checkStackPointerAlignment(temp2, 0xbad0dc01)
    181181
     182    # The jsStackLimit was previously computed in VMEntryScope using an
     183    # estimated stackPointerAtVMEntry value. Adjust the jsStackLimit by
     184    # the delta between the actual stackPointerAtVMEntry and the estimate
     185    # that we used previously.
     186    subp VM::stackPointerAtVMEntry[vm], sp, temp2
     187    subp VM::m_jsStackLimit[vm], temp2, temp2
     188    storep temp2, VM::m_jsStackLimit[vm]
     189    storep sp, VM::stackPointerAtVMEntry[vm]
     190
    182191    # The stack host zone ensures that we have adequate space for the
    183192    # VMEntrySentinelFrame. Proceed with allocating and initializing the
     
    211220    end
    212221
     222    storep 0, VM::stackPointerAtVMEntry[vm]
    213223    cCall2(_llint_throw_stack_overflow_error, vm, protoCallFrame)
    214224    callToJavaScriptEpilogue()
  • branches/jsCStack/Source/JavaScriptCore/runtime/VM.cpp

    r161174 r161180  
    168168    , clientData(0)
    169169    , topCallFrame(CallFrame::noCaller())
     170    , stackPointerAtVMEntry(0)
    170171    , arrayConstructorTable(adoptPtr(new HashTable(JSC::arrayConstructorTable)))
    171172    , arrayPrototypeTable(adoptPtr(new HashTable(JSC::arrayPrototypeTable)))
  • branches/jsCStack/Source/JavaScriptCore/runtime/VM.h

    r161174 r161180  
    228228        ClientData* clientData;
    229229        ExecState* topCallFrame;
     230        void* stackPointerAtVMEntry;
    230231        Watchdog watchdog;
    231232
  • branches/jsCStack/Source/JavaScriptCore/runtime/VMEntryScope.cpp

    r161174 r161180  
    2727#include "VMEntryScope.h"
    2828
     29#include "Options.h"
    2930#include "VM.h"
    3031#include <wtf/StackBounds.h>
     
    3637    , m_stack(wtfThreadData().stack())
    3738    , m_globalObject(globalObject)
     39    , m_prevStackUsage(0)
    3840    , m_prevFirstEntryScope(vm.firstEntryScope)
    3941    , m_prevTopEntryScope(vm.topEntryScope)
     
    4345#endif
    4446    , m_prevLastStackTop(vm.lastStackTop())
     47    , m_prevStackPointerAtVMEntry(vm.stackPointerAtVMEntry)
     48    , m_prevTopCallFrame(vm.topCallFrame)
    4549{
     50    ASSERT(wtfThreadData().stack().isGrowingDownward());
     51    ASSERT(!vm.topCallFrame || currentStackPointer() <= reinterpret_cast<char*>(vm.topCallFrame->topOfFrame()));
     52
     53    // Step 1: Compute the stack usage of the last VM entry before we install
     54    // the current entry scope below.
     55    if (vm.topEntryScope) {
     56        char* topOfStack = reinterpret_cast<char*>(vm.topCallFrame->topOfFrame());
     57        m_prevStackUsage = vm.topEntryScope->stackUsageFor(topOfStack);
     58    }
     59
     60    // Step 2: Install the current entry scope.
    4661    if (!vm.firstEntryScope) {
    4762#if ENABLE(ASSEMBLER)
     
    5570        vm.resetDateCache();
    5671    }
     72    vm.stackPointerAtVMEntry = 0;
    5773    vm.topEntryScope = this;
    5874
     
    6076    vm.clearExceptionStack();
    6177
     78    vm.setLastStackTop(m_stack.origin());
     79
     80    // Step 3: Compute the stack limit using the installed entry scope.
    6281    updateStackLimits();
    63     vm.setLastStackTop(m_stack.origin());
    6482}
    6583
     
    7391#endif
    7492    m_vm.setLastStackTop(m_prevLastStackTop);
     93    m_vm.stackPointerAtVMEntry = m_prevStackPointerAtVMEntry;
     94    m_vm.topCallFrame = m_prevTopCallFrame;
     95}
     96
     97size_t VMEntryScope::stackUsageFor(char* topOfStack) const
     98{
     99    size_t currentStackUsage = 0;
     100    ASSERT(m_vm.stackPointerAtVMEntry);
     101    char* startOfStack = reinterpret_cast<char*>(m_vm.stackPointerAtVMEntry);
     102    ASSERT(topOfStack <= startOfStack);
     103    currentStackUsage = startOfStack - topOfStack;
     104
     105    ASSERT(Options::maxStackSize() >= m_prevStackUsage + currentStackUsage);
     106    return m_prevStackUsage + currentStackUsage;
    75107}
    76108
    77109void VMEntryScope::updateStackLimits()
    78110{
     111    ASSERT(wtfThreadData().stack().isGrowingDownward());
     112    char* topOfStack = currentStackPointer();
     113
    79114#if !ENABLE(LLINT_C_LOOP)
    80     void* jsStackLimit = m_stack.recursionLimit(requiredCapacity(JSStackCapacity));
     115    char* topOfJSStack = m_vm.topCallFrame ? reinterpret_cast<char*>(m_vm.topCallFrame->topOfFrame()) : topOfStack;
     116
     117    // If we have not re-entered the VM yet via callToJavaScript / callToNativeFunction,
     118    // then stackPointerAtVMEntry will not have been set up yet. Instead, we'll
     119    // compute the stack limit relative to the current topOfJSStack (as an estimate
     120    // of stackPointerAtVMEntry). When we enter callToJavaScript later, we'll adjust
     121    // the stack limit with the delta between the actual stackPointerAtVMEntry and
     122    // the estimate value that we use here.
     123    if (!m_vm.stackPointerAtVMEntry)
     124        m_vm.stackPointerAtVMEntry = topOfJSStack;
     125
     126    void* jsStackLimit = m_stack.recursionLimit(requiredCapacity(topOfJSStack, JSStackCapacity));
     127#ifndef NDEBUG
     128    char* startOfStack = reinterpret_cast<char*>(m_vm.stackPointerAtVMEntry);
     129    char* stackLimit = reinterpret_cast<char*>(jsStackLimit);
     130    ASSERT(m_prevStackUsage + (startOfStack - stackLimit) <= Options::maxStackSize());
     131#endif
    81132    m_vm.setJSStackLimit(jsStackLimit);
    82 #endif
    83     void* nativeStackLimit = m_stack.recursionLimit(requiredCapacity(NativeStackCapacity));
     133
     134    // Some sanity checks for our pointers into the stack:
     135    ASSERT(m_vm.interpreter->stack().containsAddress(reinterpret_cast<Register*>(m_vm.stackPointerAtVMEntry)));
     136    ASSERT(m_vm.interpreter->stack().containsAddress(reinterpret_cast<Register*>(topOfJSStack)));
     137    ASSERT(currentStackPointer() <= topOfJSStack);
     138    ASSERT(topOfJSStack <= m_vm.stackPointerAtVMEntry);
     139#endif // !ENABLE(LLINT_C_LOOP)
     140
     141    void* nativeStackLimit = m_stack.recursionLimit(requiredCapacity(topOfStack, NativeStackCapacity));
    84142    m_vm.setStackLimit(nativeStackLimit);
    85143}
    86144
    87 size_t VMEntryScope::requiredCapacity(CapacityType type) const
     145char* VMEntryScope::currentStackPointer() const
    88146{
     147    char* p;
     148#if ENABLE(LLINT_C_LOOP)
     149    p = reinterpret_cast<char*>(m_vm.topCallFrame->topOfFrame());
     150#else
     151    p = reinterpret_cast<char*>(&p);
     152#endif
     153    return p;
     154}
     155
     156size_t VMEntryScope::requiredCapacity(char* topOfStack, CapacityType type) const
     157{
     158    ASSERT(m_stack.isGrowingDownward());
     159
     160    size_t excessCStackSize = 0;
     161#if !ENABLE(LLINT_C_LOOP)
     162    if (type == JSStackCapacity) {
     163        ASSERT(Options::maxStackSize() >= stackUsageFor(topOfStack));
     164        size_t availableJSStack = Options::maxStackSize() - stackUsageFor(topOfStack);
     165
     166        char* bottomOfStack = reinterpret_cast<char*>(m_stack.origin());
     167        size_t availableCStack = m_stack.size() - (bottomOfStack - topOfStack);
     168        if (availableCStack > availableJSStack)
     169            excessCStackSize = availableCStack - availableJSStack;
     170    }
     171#else
    89172    UNUSED_PARAM(type);
     173    ASSERT(type == NativeStackCapacity);
     174#endif
    90175
    91176    // We require a smaller stack budget for the error stack. This is to allow
     
    101186    Interpreter* interpreter = m_vm.interpreter;
    102187    size_t requiredCapacity = interpreter->isInErrorHandlingMode() ? errorModeRequiredStack : requiredStack;
     188    requiredCapacity += excessCStackSize;
     189
    103190    RELEASE_ASSERT(m_stack.size() >= requiredCapacity);
    104191    return requiredCapacity;
  • branches/jsCStack/Source/JavaScriptCore/runtime/VMEntryScope.h

    r161174 r161180  
    4949        NativeStackCapacity,
    5050    };
    51     size_t requiredCapacity(CapacityType) const;
     51    size_t requiredCapacity(char* topOfStack, CapacityType) const;
     52    char* currentStackPointer() const;
     53    size_t stackUsageFor(char* topOfStack) const;
    5254
    5355    VM& m_vm;
     
    5557    StackBounds m_stack;
    5658    JSGlobalObject* m_globalObject;
     59    size_t m_prevStackUsage;
    5760
    5861    // The following pointers may point to a different thread's stack.
     
    6467#endif
    6568    void* m_prevLastStackTop;
     69    void* m_prevStackPointerAtVMEntry;
     70    ExecState* m_prevTopCallFrame;
    6671};
    6772
Note: See TracChangeset for help on using the changeset viewer.