Skip to content

Commit 752ab90

Browse files
committed
[JSC] Deeply nested destructuring pattern crashes bytecode generation
https://bugs.webkit.org/show_bug.cgi?id=322333 Reviewed by Yusuke Suzuki. The parser guards its own recursion and accepts destructuring patterns nested ~20k levels deep, but ArrayPatternNode::bindValue recurses into nested patterns with no stack check. Each level costs about 1KB of native stack for the iterator temporaries and try/finally machinery, so a pattern several thousand levels deep overflows the stack before the parser's limit is reached: let [[[ /* 12000 levels */ z ]]] = []; // SIGSEGV ObjectPatternNode::bindValue has the same unbounded recursion. Add the isSafeToRecurse() check that emitNode() already performs to the top of both bindValue implementations. On failure, bytecode generation reports ParserError::OutOfMemory and the caller sees a catchable RangeError, matching other engines. Test: JSTests/stress/destructuring-pattern-deep-recursion.js * JSTests/stress/destructuring-pattern-deep-recursion.js: Added. (shouldThrowRangeError): * Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp: (JSC::ArrayPatternNode::bindValue const): (JSC::ObjectPatternNode::bindValue const): Canonical link: https://commits.webkit.org/319778@main
1 parent 1330d57 commit 752ab90

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function shouldThrowRangeError(script) {
2+
let error;
3+
try {
4+
(0, eval)(script);
5+
} catch (e) {
6+
error = e;
7+
}
8+
if (!(error instanceof RangeError))
9+
throw new Error("Expected RangeError, got " + error);
10+
}
11+
12+
const depth = 12000;
13+
const open = "[".repeat(depth);
14+
const close = "]".repeat(depth);
15+
16+
shouldThrowRangeError("let " + open + "z" + close + " = [];");
17+
shouldThrowRangeError("var " + open + "z" + close + " = [];");
18+
shouldThrowRangeError("(function (" + open + "z" + close + ") {})([]);");
19+
shouldThrowRangeError("try { throw []; } catch (" + open + "z" + close + ") {}");
20+
shouldThrowRangeError("let " + "{a:".repeat(depth) + "z" + "}".repeat(depth) + " = {};");
21+
22+
let [[[a]]] = [[[42]]];
23+
if (a !== 42)
24+
throw new Error("shallow array destructuring broken");
25+
let { b: { c } } = { b: { c: 7 } };
26+
if (c !== 7)
27+
throw new Error("shallow object destructuring broken");

‎Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5811,6 +5811,11 @@ static void assignDefaultValueIfUndefined(BytecodeGenerator& generator, Register
58115811

58125812
void ArrayPatternNode::bindValue(BytecodeGenerator& generator, RegisterID* rhs) const
58135813
{
5814+
if (!generator.vm().isSafeToRecurse()) [[unlikely]] {
5815+
generator.emitThrowExpressionTooDeepException();
5816+
return;
5817+
}
5818+
58145819
RefPtr<RegisterID> iterable = rhs;
58155820
RefPtr<RegisterID> iterator = generator.newTemporary();
58165821
RefPtr<RegisterID> nextOrIndex = generator.newTemporary();
@@ -6000,6 +6005,11 @@ void ObjectPatternNode::toString(StringBuilder& builder) const
60006005

60016006
void ObjectPatternNode::bindValue(BytecodeGenerator& generator, RegisterID* rhs) const
60026007
{
6008+
if (!generator.vm().isSafeToRecurse()) [[unlikely]] {
6009+
generator.emitThrowExpressionTooDeepException();
6010+
return;
6011+
}
6012+
60036013
const Identifier* firstPropertyName = nullptr;
60046014
if (!m_targetPatterns.isEmpty()) {
60056015
const auto& firstTarget = m_targetPatterns[0];

0 commit comments

Comments
 (0)