Add a stack overflow check in Yarr::ByteCompiler::emitDisjunction().
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Nov 2019 16:58:49 +0000 (16:58 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Nov 2019 16:58:49 +0000 (16:58 +0000)
https://bugs.webkit.org/show_bug.cgi?id=203936
<rdar://problem/56624724>

Reviewed by Saam Barati.

JSTests:

This issue originally manifested as incorrect-exception-assertion-in-operationRegExpExecNonGlobalOrSticky.js
failing on iOS devices due to its smaller stack.  We adapted the original test
here using $vm.callWithStackSize() to reproduce the issue on x86_64 though it
has a much larger physical stack to work with.  This new test will crash while
stepping on the guard page beyond the end of the stack if the fix is not applied.

* stress/stack-overflow-in-yarr-byteCompile.js: Added.

Source/JavaScriptCore:

Basically, any functions below Yarr::ByteCompiler::compile() that recurses need
to check if it's safe to recurse before doing so.  This patch adds the stack
checks in Yarr::ByteCompiler::compile() because it is the entry point to this
sub-system, and Yarr::ByteCompiler::emitDisjunction() because it is the only
function that recurses.  All other functions called below compile() are either
leaf functions or have shallow stack usage.  Hence, their stack needs are covered
by the DefaultReservedZone, and they do not need stack checks.

This patch also does the following:
1. Added $vm.callWithStackSize() which can be used to call a test function near
   the end of the physical stack.  This enables is to simulate the smaller stack
   size of more resource constrained devices.

   $vm.callWithStackSize() uses inline asm to adjust the stack pointer and
   does the callback via the JIT probe trampoline.

2. Added the --disableOptionsFreezingForTesting to the jsc shell to make it
   possible to disable freezing of JSC options.  $vm.callWithStackSize() relies
   on this to modify the VM's stack limits.

3. Removed the inline modifier on VM::updateStackLimits() so that we can call it
   from $vm.callWithStackSize() as well.  It is not a performance critical
   function and is rarely called.

4. Added a JSDollarVMHelper class that other parts of the system can declare as
   a friend.  This gives $vm a backdoor into the private functions and fields of
   classes for its debugging work.  In this patch, we're only using it to access
   VM::updateVMStackLimits().

* jsc.cpp:
(CommandLine::parseArguments):
* runtime/VM.cpp:
(JSC::VM::updateStackLimits):
* runtime/VM.h:
* tools/JSDollarVM.cpp:
(JSC::JSDollarVMHelper::JSDollarVMHelper):
(JSC::JSDollarVMHelper::vmStackStart):
(JSC::JSDollarVMHelper::vmStackLimit):
(JSC::JSDollarVMHelper::vmSoftStackLimit):
(JSC::JSDollarVMHelper::updateVMStackLimits):
(JSC::callWithStackSizeProbeFunction):
(JSC::functionCallWithStackSize):
(JSC::JSDollarVM::finishCreation):
(IGNORE_WARNINGS_BEGIN): Deleted.
* yarr/YarrInterpreter.cpp:
(JSC::Yarr::ByteCompiler::compile):
(JSC::Yarr::ByteCompiler::emitDisjunction):
(JSC::Yarr::ByteCompiler::isSafeToRecurse):

Source/WTF:

1. Add a StackCheck utility class so that we don't have to keep reinventing this
   every time we need to add stack checking somewhere.
2. Rename some arguments and constants in StackBounds to be more descriptive of
   what they actually are.

* WTF.xcodeproj/project.pbxproj:
* wtf/CMakeLists.txt:
* wtf/StackBounds.h:
(WTF::StackBounds::recursionLimit const):
* wtf/StackCheck.h: Added.
(WTF::StackCheck::StackCheck):
(WTF::StackCheck::isSafeToRecurse):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@252239 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
JSTests/ChangeLog
JSTests/stress/stack-overflow-in-yarr-byteCompile.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/runtime/VM.cpp
Source/JavaScriptCore/runtime/VM.h
Source/JavaScriptCore/tools/JSDollarVM.cpp
Source/JavaScriptCore/yarr/YarrInterpreter.cpp
Source/WTF/ChangeLog
Source/WTF/WTF.xcodeproj/project.pbxproj
Source/WTF/wtf/CMakeLists.txt
Source/WTF/wtf/StackBounds.h
Source/WTF/wtf/StackCheck.h [new file with mode: 0644]

index 77abb4c..f3216e1 100644 (file)
@@ -1,3 +1,19 @@
+2019-11-07  Mark Lam  <mark.lam@apple.com>
+
+        Add a stack overflow check in Yarr::ByteCompiler::emitDisjunction().
+        https://bugs.webkit.org/show_bug.cgi?id=203936
+        <rdar://problem/56624724>
+
+        Reviewed by Saam Barati.
+
+        This issue originally manifested as incorrect-exception-assertion-in-operationRegExpExecNonGlobalOrSticky.js
+        failing on iOS devices due to its smaller stack.  We adapted the original test
+        here using $vm.callWithStackSize() to reproduce the issue on x86_64 though it
+        has a much larger physical stack to work with.  This new test will crash while
+        stepping on the guard page beyond the end of the stack if the fix is not applied.
+
+        * stress/stack-overflow-in-yarr-byteCompile.js: Added.
+
 2019-11-07  Tuomas Karkkainen  <tuomas.webkit@apple.com>
 
         didPassSomeTestsSilenty is misspelled in standalone-pre.js
diff --git a/JSTests/stress/stack-overflow-in-yarr-byteCompile.js b/JSTests/stress/stack-overflow-in-yarr-byteCompile.js
new file mode 100644 (file)
index 0000000..7295ded
--- /dev/null
@@ -0,0 +1,30 @@
+//@ requireOptions("--disableOptionsFreezingForTesting")
+//@ skip if $hostOS != "darwin" or $architecture != "x86-64"
+
+function foo() {
+    class C {
+        constructor(func) {
+            this.func = func;
+        }
+        runTest() {
+            this.func();
+        }
+    }
+    function recurseAndTest() {
+        try {
+            recurseAndTest();
+            test.runTest();
+        } catch (e) {
+        }
+    }
+    const howManyParentheses = 1000;
+    const deepRE = new RegExp('('.repeat(howManyParentheses) + ')'.repeat(howManyParentheses));
+    let test = 
+        new C(() => {
+            deepRE.exec('');
+        });
+
+    recurseAndTest();
+}
+
+$vm.callWithStackSize(foo, 100*1024)
index fbc4be9..d330be3 100644 (file)
@@ -1,3 +1,60 @@
+2019-11-07  Mark Lam  <mark.lam@apple.com>
+
+        Add a stack overflow check in Yarr::ByteCompiler::emitDisjunction().
+        https://bugs.webkit.org/show_bug.cgi?id=203936
+        <rdar://problem/56624724>
+
+        Reviewed by Saam Barati.
+
+        Basically, any functions below Yarr::ByteCompiler::compile() that recurses need
+        to check if it's safe to recurse before doing so.  This patch adds the stack
+        checks in Yarr::ByteCompiler::compile() because it is the entry point to this
+        sub-system, and Yarr::ByteCompiler::emitDisjunction() because it is the only
+        function that recurses.  All other functions called below compile() are either
+        leaf functions or have shallow stack usage.  Hence, their stack needs are covered
+        by the DefaultReservedZone, and they do not need stack checks.
+
+        This patch also does the following:
+        1. Added $vm.callWithStackSize() which can be used to call a test function near
+           the end of the physical stack.  This enables is to simulate the smaller stack
+           size of more resource constrained devices.
+
+           $vm.callWithStackSize() uses inline asm to adjust the stack pointer and
+           does the callback via the JIT probe trampoline.
+
+        2. Added the --disableOptionsFreezingForTesting to the jsc shell to make it
+           possible to disable freezing of JSC options.  $vm.callWithStackSize() relies
+           on this to modify the VM's stack limits.
+
+        3. Removed the inline modifier on VM::updateStackLimits() so that we can call it
+           from $vm.callWithStackSize() as well.  It is not a performance critical
+           function and is rarely called.
+
+        4. Added a JSDollarVMHelper class that other parts of the system can declare as
+           a friend.  This gives $vm a backdoor into the private functions and fields of
+           classes for its debugging work.  In this patch, we're only using it to access
+           VM::updateVMStackLimits().
+
+        * jsc.cpp:
+        (CommandLine::parseArguments):
+        * runtime/VM.cpp:
+        (JSC::VM::updateStackLimits):
+        * runtime/VM.h:
+        * tools/JSDollarVM.cpp:
+        (JSC::JSDollarVMHelper::JSDollarVMHelper):
+        (JSC::JSDollarVMHelper::vmStackStart):
+        (JSC::JSDollarVMHelper::vmStackLimit):
+        (JSC::JSDollarVMHelper::vmSoftStackLimit):
+        (JSC::JSDollarVMHelper::updateVMStackLimits):
+        (JSC::callWithStackSizeProbeFunction):
+        (JSC::functionCallWithStackSize):
+        (JSC::JSDollarVM::finishCreation):
+        (IGNORE_WARNINGS_BEGIN): Deleted.
+        * yarr/YarrInterpreter.cpp:
+        (JSC::Yarr::ByteCompiler::compile):
+        (JSC::Yarr::ByteCompiler::emitDisjunction):
+        (JSC::Yarr::ByteCompiler::isSafeToRecurse):
+
 2019-11-07  Tadeu Zagallo  <tzagallo@apple.com>
 
         Use fewer virtual registers in Wasm LLInt
index 0df2818..b755688 100644 (file)
@@ -2891,6 +2891,10 @@ void CommandLine::parseArguments(int argc, char** argv)
             m_destroyVM = true;
             continue;
         }
+        if (!strcmp(arg, "--disableOptionsFreezingForTesting")) {
+            Config::disableFreezingForTesting();
+            continue;
+        }
 
         static const char* timeoutMultiplierOptStr = "--timeoutMultiplier=";
         static const unsigned timeoutMultiplierOptStrLength = strlen(timeoutMultiplierOptStr);
index 3a6cde8..184b234 100644 (file)
@@ -881,7 +881,7 @@ static void preCommitStackMemory(void* stackLimit)
 }
 #endif
 
-inline void VM::updateStackLimits()
+void VM::updateStackLimits()
 {
 #if OS(WINDOWS)
     void* lastSoftStackLimit = m_softStackLimit;
index 73f9e3f..ca51444 100644 (file)
@@ -1093,6 +1093,7 @@ private:
     friend class Heap;
     friend class CatchScope;
     friend class ExceptionScope;
+    friend class JSDollarVMHelper;
     friend class ThrowScope;
     friend class VMTraps;
     friend class WTF::DoublyLinkedListNode<VM>;
index 96b0b24..b761179 100644 (file)
@@ -31,6 +31,7 @@
 #include "DOMAttributeGetterSetter.h"
 #include "DOMJITGetterSetter.h"
 #include "Debugger.h"
+#include "Error.h"
 #include "FrameTracers.h"
 #include "FunctionCodeBlock.h"
 #include "GetterSetter.h"
@@ -43,6 +44,7 @@
 #include "JSString.h"
 #include "Options.h"
 #include "Parser.h"
+#include "ProbeContext.h"
 #include "ShadowChicken.h"
 #include "Snippet.h"
 #include "SnippetParams.h"
@@ -64,6 +66,26 @@ using namespace JSC;
 
 IGNORE_WARNINGS_BEGIN("frame-address")
 
+extern "C" void ctiMasmProbeTrampoline();
+
+namespace JSC {
+
+// This class is only here as a simple way to grant JSDollarVM friend privileges
+// to all the classes that it needs special access to.
+class JSDollarVMHelper {
+public:
+    JSDollarVMHelper(VM& vm)
+        : m_vm(vm)
+    { }
+
+    void updateVMStackLimits() { return m_vm.updateStackLimits(); };
+
+private:
+    VM& m_vm;
+};
+
+} // namespace JSC
+
 namespace {
 
 // We must RELEASE_ASSERT(Options::useDollarVM()) in all JSDollarVM functions
@@ -1968,6 +1990,123 @@ static EncodedJSValue JSC_HOST_CALL functionIsHavingABadTime(JSGlobalObject* glo
     return JSValue::encode(jsBoolean(target->isHavingABadTime()));
 }
 
+// Calls the specified test function after adjusting the stack to have the specified
+// remaining size from the end of the physical stack.
+// Usage: $vm.callWithStackSize(funcToCall, desiredStackSize)
+//
+// This function will only work in test configurations, specifically, only if JSC
+// options are not frozen. For the jsc shell, the --disableOptionsFreezingForTesting
+// argument needs to be passed in on the command line.
+
+#if ENABLE(MASM_PROBE)
+static void callWithStackSizeProbeFunction(Probe::State* state)
+{
+    JSGlobalObject* globalObject = bitwise_cast<JSGlobalObject*>(state->arg);
+    JSFunction* function = bitwise_cast<JSFunction*>(state->probeFunction);
+    state->initializeStackFunction = nullptr;
+    state->initializeStackArg = nullptr;
+
+    DollarVMAssertScope assertScope;
+    VM& vm = globalObject->vm();
+
+    CallData callData;
+    CallType callType = getCallData(vm, function, callData);
+    MarkedArgumentBuffer args;
+    call(globalObject, function, callType, callData, jsUndefined(), args);
+}
+#endif // ENABLE(MASM_PROBE)
+
+static EncodedJSValue JSC_HOST_CALL functionCallWithStackSize(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    DollarVMAssertScope assertScope;
+    VM& vm = globalObject->vm();
+    JSLockHolder lock(vm);
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+#if OS(DARWIN) && CPU(X86_64)
+    constexpr bool isSupportedByPlatform = true;
+#else
+    constexpr bool isSupportedByPlatform = false;
+#endif
+
+    if (!isSupportedByPlatform)
+        return throwVMError(globalObject, throwScope, "Not supported for this platform");
+
+#if ENABLE(MASM_PROBE)
+    if (g_jscConfig.isPermanentlyFrozen || !g_jscConfig.disabledFreezingForTesting)
+        return throwVMError(globalObject, throwScope, "Options are frozen");
+
+    if (callFrame->argumentCount() < 2)
+        return throwVMError(globalObject, throwScope, "Invalid number of arguments");
+    JSValue arg0 = callFrame->argument(0);
+    JSValue arg1 = callFrame->argument(1);
+    if (!arg0.isFunction(vm))
+        return throwVMError(globalObject, throwScope, "arg0 should be a function");
+    if (!arg1.isNumber())
+        return throwVMError(globalObject, throwScope, "arg1 should be a number");
+
+    JSFunction* function = jsCast<JSFunction*>(arg0.toObject(globalObject));
+    size_t desiredStackSize = arg1.asNumber();
+
+    const StackBounds& bounds = Thread::current().stack();
+    uint8_t* currentStackPosition = bitwise_cast<uint8_t*>(currentStackPointer());
+    uint8_t* end = bitwise_cast<uint8_t*>(bounds.end());
+    uint8_t* desiredStart = end + desiredStackSize;
+    if (desiredStart >= currentStackPosition)
+        return throwVMError(globalObject, throwScope, "Unable to setup desired stack size");
+
+    JSDollarVMHelper helper(vm);
+
+    unsigned originalMaxPerThreadStackUsage = Options::maxPerThreadStackUsage();
+    void* originalVMSoftStackLimit = vm.softStackLimit();
+    void* originalVMStackLimit = vm.stackLimit();
+
+    // This is a hack to make the VM think it's stack limits are near the end
+    // of the physical stack.
+    uint8_t* vmStackStart = bitwise_cast<uint8_t*>(vm.stackPointerAtVMEntry());
+    uint8_t* vmStackEnd = vmStackStart - originalMaxPerThreadStackUsage;
+    ptrdiff_t sizeDiff = vmStackEnd - end;
+    RELEASE_ASSERT(sizeDiff >= 0);
+    RELEASE_ASSERT(sizeDiff < UINT_MAX);
+
+    Options::maxPerThreadStackUsage() = originalMaxPerThreadStackUsage + sizeDiff;
+    helper.updateVMStackLimits();
+
+#if OS(DARWIN) && CPU(X86_64)
+    __asm__ volatile (
+        "subq %[sizeDiff], %%rsp" "\n"
+        "pushq %%rax" "\n"
+        "pushq %%rcx" "\n"
+        "pushq %%rdx" "\n"
+        "pushq %%rbx" "\n"
+        "callq *%%rax" "\n"
+        "addq %[sizeDiff], %%rsp" "\n"
+        :
+        : "a" (ctiMasmProbeTrampoline)
+        , "c" (callWithStackSizeProbeFunction)
+        , "d" (function)
+        , "b" (globalObject)
+        , [sizeDiff] "rm" (sizeDiff)
+        : "memory"
+    );
+#else
+    UNUSED_PARAM(function);
+    UNUSED_PARAM(callWithStackSizeProbeFunction);
+#endif // OS(DARWIN) && CPU(X86_64)
+
+    Options::maxPerThreadStackUsage() = originalMaxPerThreadStackUsage;
+    helper.updateVMStackLimits();
+    RELEASE_ASSERT(vm.softStackLimit() == originalVMSoftStackLimit);
+    RELEASE_ASSERT(vm.stackLimit() == originalVMStackLimit);
+
+    return encodedJSUndefined();
+
+#else // not ENABLE(MASM_PROBE)
+    UNUSED_PARAM(callFrame);
+    return throwVMError(globalObject, throwScope, "Not supported for this platform");
+#endif // ENABLE(MASM_PROBE)
+}
+
 // Creates a new global object.
 // Usage: $vm.createGlobalObject()
 static EncodedJSValue JSC_HOST_CALL functionCreateGlobalObject(JSGlobalObject* globalObject, CallFrame*)
@@ -2630,6 +2769,8 @@ void JSDollarVM::finishCreation(VM& vm)
     addFunction(vm, "haveABadTime", functionHaveABadTime, 1);
     addFunction(vm, "isHavingABadTime", functionIsHavingABadTime, 1);
 
+    addFunction(vm, "callWithStackSize", functionCallWithStackSize, 2);
+
     addFunction(vm, "createGlobalObject", functionCreateGlobalObject, 0);
     addFunction(vm, "createProxy", functionCreateProxy, 1);
     addFunction(vm, "createRuntimeArray", functionCreateRuntimeArray, 0);
index c4b77ca..0f33409 100644 (file)
@@ -34,6 +34,7 @@
 #include <wtf/BumpPointerAllocator.h>
 #include <wtf/CheckedArithmetic.h>
 #include <wtf/DataLog.h>
+#include <wtf/StackCheck.h>
 #include <wtf/text/CString.h>
 #include <wtf/text/WTFString.h>
 
@@ -1694,6 +1695,11 @@ public:
 
     std::unique_ptr<BytecodePattern> compile(BumpPointerAllocator* allocator, ConcurrentJSLock* lock, ErrorCode& errorCode)
     {
+        if (UNLIKELY(!isSafeToRecurse())) {
+            errorCode = ErrorCode::TooManyDisjunctions;
+            return nullptr;
+        }
+
         regexBegin(m_pattern.m_numSubpatterns, m_pattern.m_body->m_callFrameSize, m_pattern.m_body->m_alternatives[0]->onceThrough());
         if (auto error = emitDisjunction(m_pattern.m_body, 0, 0)) {
             errorCode = error.value();
@@ -2028,6 +2034,9 @@ public:
 
     Optional<ErrorCode> WARN_UNUSED_RETURN emitDisjunction(PatternDisjunction* disjunction, Checked<unsigned, RecordOverflow> inputCountAlreadyChecked, unsigned parenthesesInputCountAlreadyChecked)
     {
+        if (UNLIKELY(!isSafeToRecurse()))
+            return ErrorCode::TooManyDisjunctions;
+
         for (unsigned alt = 0; alt < disjunction->m_alternatives.size(); ++alt) {
             auto currentCountAlreadyChecked = inputCountAlreadyChecked;
 
@@ -2405,8 +2414,11 @@ public:
 #endif
 
 private:
+    inline bool isSafeToRecurse() { return m_stackCheck.isSafeToRecurse(); }
+
     YarrPattern& m_pattern;
     std::unique_ptr<ByteDisjunction> m_bodyDisjunction;
+    StackCheck m_stackCheck;
     unsigned m_currentAlternativeIndex { 0 };
     Vector<ParenthesesStackEntry> m_parenthesesStack;
     Vector<std::unique_ptr<ByteDisjunction>> m_allParenthesesInfo;
index 2be38fc..fdbcd20 100644 (file)
@@ -1,3 +1,24 @@
+2019-11-07  Mark Lam  <mark.lam@apple.com>
+
+        Add a stack overflow check in Yarr::ByteCompiler::emitDisjunction().
+        https://bugs.webkit.org/show_bug.cgi?id=203936
+        <rdar://problem/56624724>
+
+        Reviewed by Saam Barati.
+
+        1. Add a StackCheck utility class so that we don't have to keep reinventing this
+           every time we need to add stack checking somewhere.
+        2. Rename some arguments and constants in StackBounds to be more descriptive of
+           what they actually are.
+
+        * WTF.xcodeproj/project.pbxproj:
+        * wtf/CMakeLists.txt:
+        * wtf/StackBounds.h:
+        (WTF::StackBounds::recursionLimit const):
+        * wtf/StackCheck.h: Added.
+        (WTF::StackCheck::StackCheck):
+        (WTF::StackCheck::isSafeToRecurse):
+
 2019-11-06  Mark Lam  <mark.lam@apple.com>
 
         Remove remnants of support code for an upwards growing stack.
index d09acc1..d29ad63 100644 (file)
                F72BBDB107FA424886178B9E /* SymbolImpl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SymbolImpl.cpp; sourceTree = "<group>"; };
                FE05FAE61FDB214300093230 /* DumbPtrTraits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DumbPtrTraits.h; sourceTree = "<group>"; };
                FE05FAFE1FE5007500093230 /* WTFAssertions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WTFAssertions.cpp; sourceTree = "<group>"; };
+               FE1D6D87237401CD007A5C26 /* StackCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StackCheck.h; sourceTree = "<group>"; };
                FE1E2C392240C05400F6B729 /* PtrTag.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PtrTag.cpp; sourceTree = "<group>"; };
                FE1E2C41224187C600F6B729 /* PlatformRegisters.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlatformRegisters.cpp; sourceTree = "<group>"; };
                FE3842342325CC80009DD445 /* ResourceUsage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResourceUsage.h; sourceTree = "<group>"; };
                                A8A4730D151A825B004123FF /* Spectrum.h */,
                                A8A4730E151A825B004123FF /* StackBounds.cpp */,
                                A8A4730F151A825B004123FF /* StackBounds.h */,
+                               FE1D6D87237401CD007A5C26 /* StackCheck.h */,
                                FEEA4DF8216D7BE400AC0602 /* StackPointer.cpp */,
                                FEEA4DF7216D608C00AC0602 /* StackPointer.h */,
                                0F9DAA041FD1C37B0079C5B2 /* StackShot.h */,
index 892f7a7..a10cf9d 100644 (file)
@@ -215,6 +215,7 @@ set(WTF_PUBLIC_HEADERS
     SpanningTree.h
     Spectrum.h
     StackBounds.h
+    StackCheck.h
     StackPointer.h
     StackShot.h
     StackShotProfiler.h
index 3d6f266..a63b0bd 100644 (file)
@@ -34,15 +34,15 @@ namespace WTF {
 
 class StackBounds {
     WTF_MAKE_FAST_ALLOCATED;
+public:
 
     // This 64k number was picked because a sampling of stack usage differences
     // between consecutive entries into one of the Interpreter::execute...()
     // functions was seen to be as high as 27k. Hence, 64k is chosen as a
     // conservative availability value that is not too large but comfortably
     // exceeds 27k with some buffer for error.
-    static constexpr size_t s_defaultAvailabilityDelta = 64 * 1024;
+    static constexpr size_t DefaultReservedZone = 64 * 1024;
 
-public:
     static constexpr StackBounds emptyBounds() { return StackBounds(); }
 
 #if HAVE(STACK_BOUNDS_FOR_NEW_THREAD)
@@ -82,10 +82,10 @@ public:
         return (m_origin >= p) && (p > m_bound);
     }
 
-    void* recursionLimit(size_t minAvailableDelta = s_defaultAvailabilityDelta) const
+    void* recursionLimit(size_t minReservedZone = DefaultReservedZone) const
     {
         checkConsistency();
-        return static_cast<char*>(m_bound) + minAvailableDelta;
+        return static_cast<char*>(m_bound) + minReservedZone;
     }
 
     void* recursionLimit(char* startOfUserStack, size_t maxUserStack, size_t reservedZoneSize) const
diff --git a/Source/WTF/wtf/StackCheck.h b/Source/WTF/wtf/StackCheck.h
new file mode 100644 (file)
index 0000000..861eacc
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <wtf/StackBounds.h>
+#include <wtf/Threading.h>
+
+namespace WTF {
+
+class StackCheck {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    StackCheck(const StackBounds& bounds = Thread::current().stack(), size_t minReservedZone = StackBounds::DefaultReservedZone)
+        : m_stackLimit(bounds.recursionLimit(minReservedZone))
+    { }
+
+    inline bool isSafeToRecurse() { return currentStackPointer() >= m_stackLimit; }
+
+private:
+    void* m_stackLimit;
+};
+
+} // namespace WTF
+
+using WTF::StackCheck;