Diff
Modified: trunk/JSTests/ChangeLog (259091 => 259092)
--- trunk/JSTests/ChangeLog 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/JSTests/ChangeLog 2020-03-26 23:27:57 UTC (rev 259092)
@@ -1,3 +1,21 @@
+2020-03-26 Michael Saboff <msab...@apple.com>
+
+ Refactor YARR Stack Overflow Checks
+ https://bugs.webkit.org/show_bug.cgi?id=209435
+ rdar://problem/58988252
+
+ Reviewed by Mark Lam.
+
+ Added a new test and removed a now obsolete test.
+
+ * stress/regexp-compile-oom.js: Removed because the test is no longer valid.
+ Previously when therer where different stack check mechanisims we failed different.
+ This test was based on the different failure modes. With these changes, most of
+ the contain subtests no longer throw as this test expects.
+ * stress/regexp-huge-oom.js: Added.
+ (shouldBe):
+ (shouldThrow):
+
2020-03-26 Keith Miller <keith_mil...@apple.com>
TypedArrays should more gracefully handle OOM during slowDownAndWasteMemory
Deleted: trunk/JSTests/stress/regexp-compile-oom.js (259091 => 259092)
--- trunk/JSTests/stress/regexp-compile-oom.js 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/JSTests/stress/regexp-compile-oom.js 2020-03-26 23:27:57 UTC (rev 259092)
@@ -1,99 +0,0 @@
-//@ skip if $hostOS != "darwin" or $architecture == "arm" or $architecture == "x86" or not $jitTests
-// Test that throw an OOM exception when compiling a pathological, but valid nested RegExp.
-
-var failures = [];
-
-class TestAndExpectedException
-{
- constructor(func, exception)
- {
- this.func = func;
- this.exception = exception;
- }
-
- runTest()
- {
- try {
- this.func();
- failures.push("Running " + this.func + ", expected OOM exception, but didn't get one");
- } catch (e) {
- let errStr = e.toString();
- if (errStr != this.exception)
- failures.push("Running " + this.func + ", expected: \"" + this.exception + "\" but got \"" + errStr + "\"");
- }
- }
-}
-
-function recurseAndTest(depth, testList)
-{
- // Probe stack depth
- try {
- let result = recurseAndTest(depth + 1, testList);
- if (result == 0) {
- // Call the test functions with a nearly full stack.
- for (const test of testList)
- test.runTest();
-
- return 1;
- } else if (result < 0)
- return result + 1;
- else
- return result;
- } catch (e) {
- // Go up a several frames and then call the test functions
- return -24;
- }
-
- return 1;
-}
-
-let deepRE = new RegExp("((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((x))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))");
-let deepGlobalRE = new RegExp(deepRE, "g");
-
-let matchLen = 401; // The number of parens plus 1 for the whole match.
-
-let regExpOOMError = "Error: Out of memory: Invalid regular _expression_: too many nested disjunctions";
-
-testList = [];
-
-// Test that all RegExp related APIs that compile RE's properly handle OOM.
-testList.push(new TestAndExpectedException(() => { deepRE.exec("x"); }, regExpOOMError));
-testList.push(new TestAndExpectedException(() => { deepRE.test("x"); }, regExpOOMError));
-testList.push(new TestAndExpectedException(() => { "x".match(deepRE); }, regExpOOMError));
-testList.push(new TestAndExpectedException(() => { "x".match(deepGlobalRE); }, regExpOOMError));
-testList.push(new TestAndExpectedException(() => { "x".replace(deepGlobalRE, ""); }, regExpOOMError));
-testList.push(new TestAndExpectedException(() => { "x".replace(deepGlobalRE, "X"); }, regExpOOMError));
-testList.push(new TestAndExpectedException(() => { "x".replace(deepGlobalRE, () => { return "X" }); }, regExpOOMError));
-testList.push(new TestAndExpectedException(() => { "x".search(deepRE); }, regExpOOMError));
-
-recurseAndTest(1, testList);
-
-if (failures.length) {
- print("Got the following failures:");
- for (const failure of failures)
- print(failure);
- throw "Got failures";
-}
-
-// Test that the RegExp works correctly with RegExp.exec() and RegExp.test() when there is sufficient stack space to compile it.
-let m = deepRE.exec("x");
-let matched = true;
-if (m.length != matchLen)
- matched = false
-else {
- for (i = 0; i < matchLen; i++) {
- if (m[i] != "x")
- matched = false;
- }
-}
-
-if (!matched) {
- let expectedMatch = [];
- for (i = 0; i < matchLen; i++)
- expectedMatch[i] = "x";
-
- throw "Expected RegExp.exec(...) to be [" + expectedMatch + "] but got [" + m + "]";
-}
-
-if (!deepRE.test("x"))
- throw "Expected RegExp.test(...) to be true, but was false";
Added: trunk/JSTests/stress/regexp-huge-oom.js (0 => 259092)
--- trunk/JSTests/stress/regexp-huge-oom.js (rev 0)
+++ trunk/JSTests/stress/regexp-huge-oom.js 2020-03-26 23:27:57 UTC (rev 259092)
@@ -0,0 +1,33 @@
+//@ skip if $memoryLimited
+// Test that throw an OOM exception when compiling / executing a pathologically large RegExp's
+
+function shouldBe(actual, expected)
+{
+ if (actual !== expected)
+ throw new Error("Bad value: , expected \"" + expected + "\", but got \"" + actual + "\"");
+}
+
+function shouldThrow(run, errorType)
+{
+ let actual;
+ var hadError = false;
+
+ try {
+ actual = run();
+ } catch (e) {
+ hadError = true;
+ actual = e;
+ }
+
+ if (!hadError)
+ throw new Error("Expected " + run + "() to throw " + errorType.name + ", but did not throw.");
+ if (!(actual instanceof errorType))
+ throw new Error("Expeced " + run + "() to throw " + errorType.name + " , but threw '" + actual + "'");
+}
+
+// This should throw during pattern compilation.
+shouldThrow(() => RegExp('a?'.repeat(2**19) + 'b').exec('x'), SyntaxError);
+
+// This test should fail execution in the JIT'ed code and then fall back to the interpreter ans succeed.
+shouldBe(RegExp('a?'.repeat(2**19)).exec('x')[0], "");
+
Modified: trunk/LayoutTests/ChangeLog (259091 => 259092)
--- trunk/LayoutTests/ChangeLog 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/LayoutTests/ChangeLog 2020-03-26 23:27:57 UTC (rev 259092)
@@ -1,3 +1,19 @@
+2020-03-26 Michael Saboff <msab...@apple.com>
+
+ Refactor YARR Stack Overflow Checks
+ https://bugs.webkit.org/show_bug.cgi?id=209435
+ rdar://problem/58988252
+
+ Reviewed by Mark Lam.
+
+ Updated test for improved stack overflow checking.
+
+ * js/script-tests/stack-overflow-regexp.js:
+ (shouldThrow.recursiveCall):
+ (shouldThrow):
+ (recursiveCall):
+ * js/stack-overflow-regexp-expected.txt:
+
2020-03-26 Jason Lawrence <lawrenc...@apple.com>
[ Mac wk2 Release ] tiled-drawing/scrolling/fixed/four-bars-zoomed.html is flaky failing.
Modified: trunk/LayoutTests/js/script-tests/stack-overflow-regexp.js (259091 => 259092)
--- trunk/LayoutTests/js/script-tests/stack-overflow-regexp.js 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/LayoutTests/js/script-tests/stack-overflow-regexp.js 2020-03-26 23:27:57 UTC (rev 259092)
@@ -1,15 +1,16 @@
// https://bugs.webkit.org/show_bug.cgi?id=190755
//@ skip if $architecture == "arm" and $hostOS == "linux"
+// &&&&
description('Test that we do not overflow the stack while handling regular expressions');
// Base case.
-shouldThrow('new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")"))', '"Error: Out of memory: Invalid regular _expression_: too many nested disjunctions"');
+shouldThrow('new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")"))', '"Error: Out of memory: Invalid regular _expression_: too many nested disjunctions"');
{ // Verify that a deep JS stack does not help avoiding the error.
function recursiveCall(depth) {
if (!(depth % 10)) {
debug("Creating RegExp at depth " + depth);
- shouldThrow('new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")"))', '"Error: Out of memory: Invalid regular _expression_: too many nested disjunctions"');
+ shouldThrow('new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")"))', '"Error: Out of memory: Invalid regular _expression_: too many nested disjunctions"');
}
if (depth < 100) {
recursiveCall(depth + 1);
@@ -20,12 +21,12 @@
{ // Have the deepest nested subpattern surrounded by other expressions.
var _expression_ = "";
- for (let i = 0; i < 50000; ++i) {
+ for (let i = 0; i < 500000; ++i) {
_expression_ += "((a)(";
}
_expression_ += "b";
- for (let i = 0; i < 50000; ++i) {
+ for (let i = 0; i < 500000; ++i) {
_expression_ += ")(c))";
}
- shouldThrow('new RegExp(_expression_)', '"Error: Out of memory: Invalid regular _expression_: too many nested disjunctions"');
+ shouldThrow('new RegExp(_expression_)', '"SyntaxError: Invalid regular _expression_: regular _expression_ too large"');
}
Modified: trunk/LayoutTests/js/stack-overflow-regexp-expected.txt (259091 => 259092)
--- trunk/LayoutTests/js/stack-overflow-regexp-expected.txt 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/LayoutTests/js/stack-overflow-regexp-expected.txt 2020-03-26 23:27:57 UTC (rev 259092)
@@ -3,30 +3,30 @@
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 0
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 10
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 20
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 30
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 40
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 50
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 60
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 70
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 80
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 90
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
Creating RegExp at depth 100
-PASS new RegExp(Array(50000).join("(") + "a" + Array(50000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
-PASS new RegExp(_expression_) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(Array(500000).join("(") + "a" + Array(500000).join(")")) threw exception Error: Out of memory: Invalid regular _expression_: too many nested disjunctions.
+PASS new RegExp(_expression_) threw exception SyntaxError: Invalid regular _expression_: regular _expression_ too large.
PASS successfullyParsed is true
TEST COMPLETE
Modified: trunk/Source/_javascript_Core/ChangeLog (259091 => 259092)
--- trunk/Source/_javascript_Core/ChangeLog 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/ChangeLog 2020-03-26 23:27:57 UTC (rev 259092)
@@ -1,3 +1,59 @@
+2020-03-26 Michael Saboff <msab...@apple.com>
+
+ Refactor YARR Stack Overflow Checks
+ https://bugs.webkit.org/show_bug.cgi?id=209435
+ rdar://problem/58988252
+
+ Reviewed by Mark Lam.
+
+ Refactored stack checks in YARR code including adding a stack check to the YARR JIT'ed code.
+ The C++ code including the parser, byte code compiler and interpreter now all use StackCheck.
+ The JIT'ed code needs a stack limit passed via a parameter since the JIT'ed code can be
+ called from the compiler thread when compiling DFG / FTL code.
+
+ Instead of adding a new parameter, consolidated the two pattern context buffer values, buffer
+ pointer and size, with the new stack limit into a new MatchingContextHolder, an RAII object.
+ The MatchingContextHolder constructor uses either the VM stack limit or the current thread's
+ stack limit depending on how it is called.
+
+ * runtime/RegExp.cpp:
+ (JSC::RegExp::finishCreation):
+ (JSC::RegExp::byteCodeCompileIfNecessary):
+ (JSC::RegExp::compile):
+ (JSC::RegExp::matchConcurrently):
+ (JSC::RegExp::compileMatchOnly):
+ * runtime/RegExp.h:
+ * runtime/RegExpInlines.h:
+ (JSC::RegExp::matchInline):
+ (JSC::PatternContextBufferHolder::PatternContextBufferHolder): Deleted.
+ (JSC::PatternContextBufferHolder::~PatternContextBufferHolder): Deleted.
+ (JSC::PatternContextBufferHolder::buffer): Deleted.
+ (JSC::PatternContextBufferHolder::size): Deleted.
+ (): Deleted.
+ * yarr/Yarr.h:
+ * yarr/YarrInterpreter.cpp:
+ (JSC::Yarr::Interpreter::matchDisjunction):
+ (JSC::Yarr::Interpreter::isSafeToRecurse):
+ * yarr/YarrJIT.cpp:
+ (JSC::Yarr::MatchingContextHolder::MatchingContextHolder):
+ (JSC::Yarr::MatchingContextHolder::~MatchingContextHolder):
+ (JSC::Yarr::YarrGenerator::initParenContextFreeList):
+ (JSC::Yarr::YarrGenerator::alignCallFrameSizeInBytes):
+ (JSC::Yarr::YarrGenerator::compile):
+ (JSC::Yarr::YarrGenerator::initCallFrame): Deleted.
+ * yarr/YarrJIT.h:
+ (JSC::Yarr::MatchingContextHolder::offsetOfStackLimit):
+ (JSC::Yarr::MatchingContextHolder::offsetOfPatternContextBuffer):
+ (JSC::Yarr::MatchingContextHolder::offsetOfPatternContextBufferSize):
+ (JSC::Yarr::YarrCodeBlock::execute):
+ * yarr/YarrPattern.cpp:
+ (JSC::Yarr::YarrPatternConstructor::YarrPatternConstructor):
+ (JSC::Yarr::YarrPatternConstructor::isSafeToRecurse):
+ (JSC::Yarr::YarrPattern::compile):
+ (JSC::Yarr::YarrPattern::YarrPattern):
+ (JSC::Yarr::YarrPatternConstructor::isSafeToRecurse const): Deleted.
+ * yarr/YarrPattern.h:
+
2020-03-26 Keith Miller <keith_mil...@apple.com>
TypedArrays should more gracefully handle OOM during slowDownAndWasteMemory
Modified: trunk/Source/_javascript_Core/runtime/RegExp.cpp (259091 => 259092)
--- trunk/Source/_javascript_Core/runtime/RegExp.cpp 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/runtime/RegExp.cpp 2020-03-26 23:27:57 UTC (rev 259092)
@@ -170,7 +170,7 @@
void RegExp::finishCreation(VM& vm)
{
Base::finishCreation(vm);
- Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode, vm.stackLimit());
+ Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode);
if (!isValid()) {
m_state = ParseError;
return;
@@ -227,13 +227,10 @@
if (m_regExpBytecode)
return;
- Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode, vm->stackLimit());
+ Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode);
if (hasError(m_constructionErrorCode)) {
- RELEASE_ASSERT_NOT_REACHED();
-#if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE)
m_state = ParseError;
return;
-#endif
}
ASSERT(m_numSubpatterns == pattern.m_numSubpatterns);
@@ -248,7 +245,7 @@
{
auto locker = holdLock(cellLock());
- Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode, vm->stackLimit());
+ Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode);
if (hasError(m_constructionErrorCode)) {
m_state = ParseError;
return;
@@ -302,7 +299,9 @@
if (!hasCodeFor(s.is8Bit() ? Yarr::Char8 : Yarr::Char16))
return false;
- position = match(vm, s, startOffset, ovector);
+ position = matchInline<Vector<int>&, Yarr::MatchFrom::CompilerThread>(vm, s, startOffset, ovector);
+ if (m_state == ParseError)
+ return false;
return true;
}
@@ -310,7 +309,7 @@
{
auto locker = holdLock(cellLock());
- Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode, vm->stackLimit());
+ Yarr::YarrPattern pattern(m_patternString, m_flags, m_constructionErrorCode);
if (hasError(m_constructionErrorCode)) {
m_state = ParseError;
return;
@@ -363,7 +362,7 @@
if (!hasMatchOnlyCodeFor(s.is8Bit() ? Yarr::Char8 : Yarr::Char16))
return false;
- result = match(vm, s, startOffset);
+ result = matchInline<Yarr::MatchFrom::CompilerThread>(vm, s, startOffset);
return true;
}
Modified: trunk/Source/_javascript_Core/runtime/RegExp.h (259091 => 259092)
--- trunk/Source/_javascript_Core/runtime/RegExp.h 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/runtime/RegExp.h 2020-03-26 23:27:57 UTC (rev 259092)
@@ -86,8 +86,9 @@
bool matchConcurrently(VM&, const String&, unsigned startOffset, MatchResult&);
// Call these versions of the match functions if you're desperate for performance.
- template<typename VectorType>
+ template<typename VectorType, Yarr::MatchFrom thread = Yarr::MatchFrom::VMThread>
int matchInline(VM&, const String&, unsigned startOffset, VectorType& ovector);
+ template<Yarr::MatchFrom thread = Yarr::MatchFrom::VMThread>
MatchResult matchInline(VM&, const String&, unsigned startOffset);
unsigned numSubpatterns() const { return m_numSubpatterns; }
Modified: trunk/Source/_javascript_Core/runtime/RegExpInlines.h (259091 => 259092)
--- trunk/Source/_javascript_Core/runtime/RegExpInlines.h 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/runtime/RegExpInlines.h 2020-03-26 23:27:57 UTC (rev 259092)
@@ -86,38 +86,6 @@
return false;
}
-class PatternContextBufferHolder {
- WTF_FORBID_HEAP_ALLOCATION;
-public:
- PatternContextBufferHolder(VM& vm, bool needBuffer)
- : m_vm(vm)
- {
-#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
- if (needBuffer)
- m_buffer = m_vm.acquireRegExpPatternContexBuffer();
-#else
- UNUSED_PARAM(needBuffer);
-#endif
- }
-
- ~PatternContextBufferHolder()
- {
-#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
- if (buffer())
- m_vm.releaseRegExpPatternContexBuffer();
-#else
- UNUSED_PARAM(m_vm);
-#endif
- }
-
- void* buffer() { return m_buffer; }
- unsigned size() { return buffer() ? VM::patternContextBufferSize : 0; }
-
-private:
- VM& m_vm;
- void* m_buffer { nullptr };
-};
-
ALWAYS_INLINE void RegExp::compileIfNecessary(VM& vm, Yarr::YarrCharSize charSize)
{
if (hasCodeFor(charSize))
@@ -129,7 +97,7 @@
compile(&vm, charSize);
}
-template<typename VectorType>
+template<typename VectorType, Yarr::MatchFrom matchFrom>
ALWAYS_INLINE int RegExp::matchInline(VM& vm, const String& s, unsigned startOffset, VectorType& ovector)
{
#if ENABLE(REGEXP_TRACING)
@@ -162,19 +130,22 @@
if (m_state == JITCode) {
{
ASSERT(m_regExpJITCode);
- PatternContextBufferHolder patternContextBufferHolder(vm, m_regExpJITCode->usesPatternContextBuffer());
+ Yarr::MatchingContextHolder regExpContext(vm, m_regExpJITCode.get(), matchFrom);
if (s.is8Bit())
- result = m_regExpJITCode->execute(s.characters8(), startOffset, s.length(), offsetVector, patternContextBufferHolder.buffer(), patternContextBufferHolder.size()).start;
+ result = m_regExpJITCode->execute(s.characters8(), startOffset, s.length(), offsetVector, regExpContext).start;
else
- result = m_regExpJITCode->execute(s.characters16(), startOffset, s.length(), offsetVector, patternContextBufferHolder.buffer(), patternContextBufferHolder.size()).start;
+ result = m_regExpJITCode->execute(s.characters16(), startOffset, s.length(), offsetVector, regExpContext).start;
}
if (result == Yarr::JSRegExpJITCodeFailure) {
// JIT'ed code couldn't handle _expression_, so punt back to the interpreter.
byteCodeCompileIfNecessary(&vm);
- if (m_state == ParseError)
+ if (m_state == ParseError) {
+ if (matchFrom == Yarr::MatchFrom::CompilerThread)
+ return -1;
return throwError();
+ }
result = Yarr::interpret(m_regExpBytecode.get(), s, startOffset, reinterpret_cast<unsigned*>(offsetVector));
}
@@ -260,6 +231,7 @@
compileMatchOnly(&vm, charSize);
}
+template<Yarr::MatchFrom matchFrom>
ALWAYS_INLINE MatchResult RegExp::matchInline(VM& vm, const String& s, unsigned startOffset)
{
#if ENABLE(REGEXP_TRACING)
@@ -289,11 +261,12 @@
if (m_state == JITCode) {
{
ASSERT(m_regExpJITCode);
- PatternContextBufferHolder patternContextBufferHolder(vm, m_regExpJITCode->usesPatternContextBuffer());
+ Yarr::MatchingContextHolder regExpContext(vm, m_regExpJITCode.get(), matchFrom);
+
if (s.is8Bit())
- result = m_regExpJITCode->execute(s.characters8(), startOffset, s.length(), patternContextBufferHolder.buffer(), patternContextBufferHolder.size());
+ result = m_regExpJITCode->execute(s.characters8(), startOffset, s.length(), regExpContext);
else
- result = m_regExpJITCode->execute(s.characters16(), startOffset, s.length(), patternContextBufferHolder.buffer(), patternContextBufferHolder.size());
+ result = m_regExpJITCode->execute(s.characters16(), startOffset, s.length(), regExpContext);
}
#if ENABLE(REGEXP_TRACING)
Modified: trunk/Source/_javascript_Core/yarr/Yarr.h (259091 => 259092)
--- trunk/Source/_javascript_Core/yarr/Yarr.h 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/yarr/Yarr.h 2020-03-26 23:27:57 UTC (rev 259092)
@@ -49,6 +49,8 @@
// avoid spending exponential time on complex regular expressions.
static constexpr unsigned matchLimit = 1000000;
+enum MatchFrom { VMThread, CompilerThread };
+
enum JSRegExpResult {
JSRegExpMatch = 1,
JSRegExpNoMatch = 0,
Modified: trunk/Source/_javascript_Core/yarr/YarrInterpreter.cpp (259091 => 259092)
--- trunk/Source/_javascript_Core/yarr/YarrInterpreter.cpp 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/yarr/YarrInterpreter.cpp 2020-03-26 23:27:57 UTC (rev 259092)
@@ -1272,6 +1272,9 @@
#define currentTerm() (disjunction->terms[context->term])
JSRegExpResult matchDisjunction(ByteDisjunction* disjunction, DisjunctionContext* context, bool btrack = false)
{
+ if (UNLIKELY(!isSafeToRecurse()))
+ return JSRegExpErrorNoMemory;
+
if (!--remainingMatchCount)
return JSRegExpErrorHitLimit;
@@ -1667,10 +1670,13 @@
}
private:
+ inline bool isSafeToRecurse() { return m_stackCheck.isSafeToRecurse(); }
+
BytecodePattern* pattern;
bool unicode;
unsigned* output;
InputStream input;
+ StackCheck m_stackCheck;
WTF::BumpPointerPool* allocatorPool { nullptr };
unsigned startOffset;
unsigned remainingMatchCount;
Modified: trunk/Source/_javascript_Core/yarr/YarrJIT.cpp (259091 => 259092)
--- trunk/Source/_javascript_Core/yarr/YarrJIT.cpp 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/yarr/YarrJIT.cpp 2020-03-26 23:27:57 UTC (rev 259092)
@@ -26,7 +26,6 @@
#include "config.h"
#include "YarrJIT.h"
-#include <wtf/ASCIICType.h>
#include "LinkBuffer.h"
#include "Options.h"
#include "VM.h"
@@ -33,11 +32,42 @@
#include "Yarr.h"
#include "YarrCanonicalize.h"
#include "YarrDisassembler.h"
+#include <wtf/ASCIICType.h>
+#include <wtf/Threading.h>
+
#if ENABLE(YARR_JIT)
namespace JSC { namespace Yarr {
+MatchingContextHolder::MatchingContextHolder(VM& vm, YarrCodeBlock* yarrCodeBlock, MatchFrom matchFrom)
+ : m_vm(vm)
+{
+ if (matchFrom == MatchFrom::VMThread)
+ m_stackLimit = vm.softStackLimit();
+ else {
+ StackBounds stack = Thread::current().stack();
+ m_stackLimit = stack.recursionLimit(Options::reservedZoneSize());
+ }
+
+#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
+ if (yarrCodeBlock->usesPatternContextBuffer()) {
+ m_patternContextBuffer = m_vm.acquireRegExpPatternContexBuffer();
+ m_patternContextBufferSize = VM::patternContextBufferSize;
+ }
+#else
+ UNUSED_PARAM(yarrCodeBlock);
+#endif
+}
+
+MatchingContextHolder::~MatchingContextHolder()
+{
+#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
+ if (m_patternContextBuffer)
+ m_vm.releaseRegExpPatternContexBuffer();
+#endif
+}
+
template<YarrJITCompileMode compileMode>
class YarrGenerator : public YarrJITInfo, private MacroAssembler {
@@ -60,7 +90,8 @@
static const RegisterID index = ARM64Registers::x1;
static const RegisterID length = ARM64Registers::x2;
static const RegisterID output = ARM64Registers::x3;
- static const RegisterID freelistRegister = ARM64Registers::x4;
+ static const RegisterID matchingContext = ARM64Registers::x4;
+ static const RegisterID freelistRegister = ARM64Registers::x4; // Loaded from the MatchingContextHolder in the prologue.
static const RegisterID freelistSizeRegister = ARM64Registers::x5; // Only used during initialization.
// Scratch registers
@@ -101,7 +132,8 @@
static const RegisterID index = X86Registers::esi;
static const RegisterID length = X86Registers::edx;
static const RegisterID output = X86Registers::ecx;
- static const RegisterID freelistRegister = X86Registers::r8;
+ static const RegisterID matchingContext = X86Registers::r8;
+ static const RegisterID freelistRegister = X86Registers::r8; // Loaded from the MatchingContextHolder in the prologue.
static const RegisterID freelistSizeRegister = X86Registers::r9; // Only used during initialization.
#else
// If the return value doesn't fit in 64bits, its destination is pointed by rcx and the parameters are shifted.
@@ -220,6 +252,9 @@
return;
}
+ load32(Address(matchingContext, MatchingContextHolder::offsetOfPatternContextBufferSize()), freelistSizeRegister);
+ // Note that matchingContext and freelistRegister are likely the same register.
+ loadPtr(Address(matchingContext, MatchingContextHolder::offsetOfPatternContextBuffer()), freelistRegister);
Jump emptyFreeList = branchTestPtr(Zero, freelistRegister);
move(freelistRegister, parenContextPointer);
addPtr(TrustedImm32(parenContextSize), freelistRegister, nextParenContextPointer);
@@ -635,12 +670,6 @@
callFrameSize = (callFrameSize + 0x3f) & ~0x3f;
return callFrameSize;
}
- void initCallFrame()
- {
- unsigned callFrameSizeInBytes = alignCallFrameSizeInBytes(m_pattern.m_body->m_callFrameSize);
- if (callFrameSizeInBytes)
- subPtr(Imm32(callFrameSizeInBytes), stackPointerRegister);
- }
void removeCallFrame()
{
unsigned callFrameSizeInBytes = alignCallFrameSizeInBytes(m_pattern.m_body->m_callFrameSize);
@@ -3851,6 +3880,30 @@
generateFailReturn();
hasInput.link(this);
+ unsigned callFrameSizeInBytes = alignCallFrameSizeInBytes(m_pattern.m_body->m_callFrameSize);
+ if (callFrameSizeInBytes) {
+ // Check stack size
+ addPtr(TrustedImm32(-callFrameSizeInBytes), stackPointerRegister, regT0);
+#if CPU(X86_64) && OS(WINDOWS)
+ // matchingContext is the 5th argument, it is found on the stack.
+ RegisterID matchingContext = regT1;
+ loadPtr(Address(X86Registers::ebp, 7 * sizeof(void*)), matchingContext);
+#elif CPU(ARM_THUMB2) || CPU(MIPS)
+ // matchingContext is the 5th argument, it is found on the stack.
+ RegisterID matchingContext = regT1;
+ loadPtr(Address(stackPointerRegister, 4 * sizeof(void*)), matchingContext);
+#endif
+ Jump stackOk = branchPtr(BelowOrEqual, Address(matchingContext, MatchingContextHolder::offsetOfStackLimit()), regT0);
+
+ // Exceeded stack limit, punt to the interpreter.
+ move(TrustedImmPtr((void*)static_cast<size_t>(JSRegExpJITCodeFailure)), returnRegister);
+ move(TrustedImm32(0), returnRegister2);
+ generateReturn();
+
+ stackOk.link(this);
+ move(regT0, stackPointerRegister);
+ }
+
#ifdef JIT_UNICODE_EXPRESSIONS
if (m_decodeSurrogatePairs)
getEffectiveAddress(BaseIndex(input, length, TimesTwo), endOfStringAddress);
@@ -3869,8 +3922,6 @@
if (!m_pattern.m_body->m_hasFixedSize)
setMatchStart(index);
- initCallFrame();
-
#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
if (m_containsNestedSubpatterns) {
initParenContextFreeList();
Modified: trunk/Source/_javascript_Core/yarr/YarrJIT.h (259091 => 259092)
--- trunk/Source/_javascript_Core/yarr/YarrJIT.h 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/yarr/YarrJIT.h 2020-03-26 23:27:57 UTC (rev 259092)
@@ -29,6 +29,7 @@
#include "MacroAssemblerCodeRef.h"
#include "MatchResult.h"
+#include "VM.h"
#include "Yarr.h"
#include "YarrPattern.h"
@@ -41,6 +42,8 @@
namespace Yarr {
+class YarrCodeBlock;
+
enum class JITFailureReason : uint8_t {
DecodeSurrogatePair,
BackReference,
@@ -52,15 +55,35 @@
ExecutableMemoryAllocationFailure,
};
+class MatchingContextHolder {
+ WTF_FORBID_HEAP_ALLOCATION;
+public:
+ MatchingContextHolder(VM&, YarrCodeBlock*, MatchFrom matchFrom = MatchFrom::VMThread);
+ ~MatchingContextHolder();
+
+ static ptrdiff_t offsetOfStackLimit() { return OBJECT_OFFSETOF(MatchingContextHolder, m_stackLimit); }
+#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
+ static ptrdiff_t offsetOfPatternContextBuffer() { return OBJECT_OFFSETOF(MatchingContextHolder, m_patternContextBuffer); }
+ static ptrdiff_t offsetOfPatternContextBufferSize() { return OBJECT_OFFSETOF(MatchingContextHolder, m_patternContextBufferSize); }
+#endif
+
+private:
+ VM& m_vm;
+ void* m_stackLimit;
+#if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
+ void* m_patternContextBuffer { nullptr };
+ unsigned m_patternContextBufferSize { 0 };
+#endif
+};
+
class YarrCodeBlock {
WTF_MAKE_FAST_ALLOCATED;
WTF_MAKE_NONCOPYABLE(YarrCodeBlock);
- // Technically freeParenContext and parenContextSize are only used if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS) is set. Fortunately, all the calling conventions we support have caller save argument registers.
- using YarrJITCode8 = EncodedMatchResult (*)(const LChar* input, unsigned start, unsigned length, int* output, void* freeParenContext, unsigned parenContextSize) YARR_CALL;
- using YarrJITCode16 = EncodedMatchResult (*)(const UChar* input, unsigned start, unsigned length, int* output, void* freeParenContext, unsigned parenContextSize) YARR_CALL;
- using YarrJITCodeMatchOnly8 = EncodedMatchResult (*)(const LChar* input, unsigned start, unsigned length, void*, void* freeParenContext, unsigned parenContextSize) YARR_CALL;
- using YarrJITCodeMatchOnly16 = EncodedMatchResult (*)(const UChar* input, unsigned start, unsigned length, void*, void* freeParenContext, unsigned parenContextSize) YARR_CALL;
+ using YarrJITCode8 = EncodedMatchResult (*)(const LChar* input, unsigned start, unsigned length, int* output, MatchingContextHolder& matchingContext) YARR_CALL;
+ using YarrJITCode16 = EncodedMatchResult (*)(const UChar* input, unsigned start, unsigned length, int* output, MatchingContextHolder& matchingContext) YARR_CALL;
+ using YarrJITCodeMatchOnly8 = EncodedMatchResult (*)(const LChar* input, unsigned start, unsigned length, void*, MatchingContextHolder& matchingContext) YARR_CALL;
+ using YarrJITCodeMatchOnly16 = EncodedMatchResult (*)(const UChar* input, unsigned start, unsigned length, void*, MatchingContextHolder& matchingContext) YARR_CALL;
public:
YarrCodeBlock() = default;
@@ -83,28 +106,28 @@
void setUsesPatternContextBuffer() { m_usesPatternContextBuffer = true; }
#endif
- MatchResult execute(const LChar* input, unsigned start, unsigned length, int* output, void* freeParenContext, unsigned parenContextSize)
+ MatchResult execute(const LChar* input, unsigned start, unsigned length, int* output, MatchingContextHolder& matchingContext)
{
ASSERT(has8BitCode());
- return MatchResult(untagCFunctionPtr<YarrJITCode8, Yarr8BitPtrTag>(m_ref8.code().executableAddress())(input, start, length, output, freeParenContext, parenContextSize));
+ return MatchResult(untagCFunctionPtr<YarrJITCode8, Yarr8BitPtrTag>(m_ref8.code().executableAddress())(input, start, length, output, matchingContext));
}
- MatchResult execute(const UChar* input, unsigned start, unsigned length, int* output, void* freeParenContext, unsigned parenContextSize)
+ MatchResult execute(const UChar* input, unsigned start, unsigned length, int* output, MatchingContextHolder& matchingContext)
{
ASSERT(has16BitCode());
- return MatchResult(untagCFunctionPtr<YarrJITCode16, Yarr16BitPtrTag>(m_ref16.code().executableAddress())(input, start, length, output, freeParenContext, parenContextSize));
+ return MatchResult(untagCFunctionPtr<YarrJITCode16, Yarr16BitPtrTag>(m_ref16.code().executableAddress())(input, start, length, output, matchingContext));
}
- MatchResult execute(const LChar* input, unsigned start, unsigned length, void* freeParenContext, unsigned parenContextSize)
+ MatchResult execute(const LChar* input, unsigned start, unsigned length, MatchingContextHolder& matchingContext)
{
ASSERT(has8BitCodeMatchOnly());
- return MatchResult(untagCFunctionPtr<YarrJITCodeMatchOnly8, YarrMatchOnly8BitPtrTag>(m_matchOnly8.code().executableAddress())(input, start, length, 0, freeParenContext, parenContextSize));
+ return MatchResult(untagCFunctionPtr<YarrJITCodeMatchOnly8, YarrMatchOnly8BitPtrTag>(m_matchOnly8.code().executableAddress())(input, start, length, 0, matchingContext));
}
- MatchResult execute(const UChar* input, unsigned start, unsigned length, void* freeParenContext, unsigned parenContextSize)
+ MatchResult execute(const UChar* input, unsigned start, unsigned length, MatchingContextHolder& matchingContext)
{
ASSERT(has16BitCodeMatchOnly());
- return MatchResult(untagCFunctionPtr<YarrJITCodeMatchOnly16, YarrMatchOnly16BitPtrTag>(m_matchOnly16.code().executableAddress())(input, start, length, 0, freeParenContext, parenContextSize));
+ return MatchResult(untagCFunctionPtr<YarrJITCodeMatchOnly16, YarrMatchOnly16BitPtrTag>(m_matchOnly16.code().executableAddress())(input, start, length, 0, matchingContext));
}
#if ENABLE(REGEXP_TRACING)
Modified: trunk/Source/_javascript_Core/yarr/YarrPattern.cpp (259091 => 259092)
--- trunk/Source/_javascript_Core/yarr/YarrPattern.cpp 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/yarr/YarrPattern.cpp 2020-03-26 23:27:57 UTC (rev 259092)
@@ -33,7 +33,7 @@
#include "YarrParser.h"
#include <wtf/DataLog.h>
#include <wtf/Optional.h>
-#include <wtf/StackPointer.h>
+#include <wtf/StackCheck.h>
#include <wtf/Threading.h>
#include <wtf/Vector.h>
@@ -436,10 +436,9 @@
class YarrPatternConstructor {
public:
- YarrPatternConstructor(YarrPattern& pattern, void* stackLimit)
+ YarrPatternConstructor(YarrPattern& pattern)
: m_pattern(pattern)
, m_characterClassConstructor(pattern.ignoreCase(), pattern.unicode() ? CanonicalMode::Unicode : CanonicalMode::UCS2)
- , m_stackLimit(stackLimit)
{
auto body = makeUnique<PatternDisjunction>();
m_pattern.m_body = body.get();
@@ -1101,27 +1100,20 @@
ErrorCode error() { return m_error; }
private:
- bool isSafeToRecurse() const
- {
- if (!m_stackLimit)
- return true;
- int8_t* curr = reinterpret_cast<int8_t*>(currentStackPointer());
- int8_t* limit = reinterpret_cast<int8_t*>(m_stackLimit);
- return curr >= limit;
- }
+ inline bool isSafeToRecurse() { return m_stackCheck.isSafeToRecurse(); }
YarrPattern& m_pattern;
PatternAlternative* m_alternative;
CharacterClassConstructor m_characterClassConstructor;
- void* m_stackLimit;
+ StackCheck m_stackCheck;
ErrorCode m_error { ErrorCode::NoError };
bool m_invertCharacterClass;
bool m_invertParentheticalAssertion { false };
};
-ErrorCode YarrPattern::compile(const String& patternString, void* stackLimit)
+ErrorCode YarrPattern::compile(const String& patternString)
{
- YarrPatternConstructor constructor(*this, stackLimit);
+ YarrPatternConstructor constructor(*this);
{
ErrorCode error = parse(constructor, patternString, unicode());
@@ -1148,7 +1140,7 @@
return ErrorCode::NoError;
}
-YarrPattern::YarrPattern(const String& pattern, OptionSet<Flags> flags, ErrorCode& error, void* stackLimit)
+YarrPattern::YarrPattern(const String& pattern, OptionSet<Flags> flags, ErrorCode& error)
: m_containsBackreferences(false)
, m_containsBOL(false)
, m_containsUnsignedLengthPattern(false)
@@ -1157,7 +1149,7 @@
, m_flags(flags)
{
ASSERT(m_flags != Flags::DeletedValue);
- error = compile(pattern, stackLimit);
+ error = compile(pattern);
}
void indentForNestingLevel(PrintStream& out, unsigned nestingDepth)
Modified: trunk/Source/_javascript_Core/yarr/YarrPattern.h (259091 => 259092)
--- trunk/Source/_javascript_Core/yarr/YarrPattern.h 2020-03-26 23:10:36 UTC (rev 259091)
+++ trunk/Source/_javascript_Core/yarr/YarrPattern.h 2020-03-26 23:27:57 UTC (rev 259092)
@@ -386,7 +386,7 @@
struct YarrPattern {
- JS_EXPORT_PRIVATE YarrPattern(const String& pattern, OptionSet<Flags>, ErrorCode&, void* stackLimit = nullptr);
+ JS_EXPORT_PRIVATE YarrPattern(const String& pattern, OptionSet<Flags>, ErrorCode&);
void resetForReparsing()
{
@@ -543,7 +543,7 @@
HashMap<String, unsigned> m_namedGroupToParenIndex;
private:
- ErrorCode compile(const String& patternString, void* stackLimit);
+ ErrorCode compile(const String& patternString);
CharacterClass* anycharCached { nullptr };
CharacterClass* newlineCached { nullptr };