Skip to content

bytecompiler: guard recursion in Array/ObjectPatternNode::bindValue - #355

Open
robobun wants to merge 1 commit into
mainfrom
farm/8188f1bf/destructuring-bindvalue-stack-check
Open

bytecompiler: guard recursion in Array/ObjectPatternNode::bindValue#355
robobun wants to merge 1 commit into
mainfrom
farm/8188f1bf/destructuring-bindvalue-stack-check

Conversation

@robobun

@robobun robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

A deeply nested array destructuring pattern crashes the process with SIGSEGV on a release build:

const N = 12000;
const src = 'let ' + '['.repeat(N) + 'z' + ']'.repeat(N) + ' = w;';
(0, eval)(src);   // SIGSEGV

Node.js throws RangeError: Maximum call stack size exceeded for the same input.

Cause

The parser's parseDestructuringPattern guards its own recursion with failIfStackOverflow() and can parse ~20k levels before bailing. Once parsing succeeds, BytecodeGenerator walks the AST via ArrayPatternNode::bindValue, which recurses through emitTryWithFinallyThatDoesNotShadowException and a ScopedLambda into the nested pattern's bindValue with no stack check. Each level costs roughly 1 KB of native stack (iterator / done / value temporaries, CallArguments, FinallyContext, the try-data wrapper), so a pattern ~8.5k levels deep blows through maxPerThreadStackUsage and past the OS guard page before the parser's own limit is reached.

ObjectPatternNode::bindValue has the same unbounded recursion; it happens not to overflow today only because its per-level frame is much smaller than the array path, so the parser limit trips first.

Fix

Add the same isSafeToRecurse() check that emitNode() already performs to the top of both bindValue implementations. On failure, call emitThrowExpressionTooDeepException() and return; BytecodeGenerator::generate then reports ParserError::OutOfMemory and the caller sees a catchable RangeError instead of a crash.

Verification

Against a bun release build linked with this change, every entry point that previously segfaulted (let/const declaration, for (let [[…]] of …), catch ([[…]]), function parameters, (0, eval)(…)) now throws a RangeError at all tested depths; shallow destructuring is unchanged.

…::bindValue

A deeply nested array destructuring pattern like

    let [[...[[z]]...]] = w;

crashes the process with SIGSEGV on a release build. The parser's
parseDestructuringPattern guards its recursion with failIfStackOverflow(),
but once parsing succeeds, BytecodeGenerator walks the resulting AST via
ArrayPatternNode::bindValue, which recurses through
emitTryWithFinallyThatDoesNotShadowException and a ScopedLambda into the
nested pattern's bindValue with no stack check at all. Each level costs
roughly 1 KB of stack (iterator/done/value temporaries, CallArguments,
FinallyContext, the try-data wrapper), so a pattern a few thousand levels
deep runs past the guard page before the parser's own limit is reached.

Node.js throws RangeError for the same input.

Add the same isSafeToRecurse() check that emitNode() already performs to
the top of ArrayPatternNode::bindValue and ObjectPatternNode::bindValue.
On failure, set m_expressionTooDeep and return; BytecodeGenerator::generate
then reports ParserError::OutOfMemory and the caller sees a RangeError
instead of a crash. ObjectPatternNode happens not to overflow today because
its per-level frame is smaller, but it is the same unbounded recursion and
gets the same guard.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 16 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 14a4d40b-7e30-4777-9372-15572098f2fe

📥 Commits

Reviewing files that changed from the base of the PR and between d103ebd and ebad754.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — straightforward stack-recursion guard following the existing emitNode() pattern.

What was reviewed:

  • Confirmed the guard is byte-for-byte identical to the void-returning emitNode / emitNodeInConditionContext checks in BytecodeGenerator.h:506-509, 608-611.
  • Verified generator.vm() (public, BytecodeGenerator.h:377) and emitThrowExpressionTooDeepException() (BytecodeGenerator.h:1293) are both accessible from NodesCodegen.cpp.
  • Checked that early return; from bindValue is safe — callers don't inspect any output, and emitThrowExpressionTooDeepException sets m_outOfMemoryDuringConstruction so generate() bails with ParserError::OutOfMemory.
Extended reasoning...

Overview

The PR adds a 4-line stack-depth guard at the top of ArrayPatternNode::bindValue and ObjectPatternNode::bindValue in NodesCodegen.cpp. Deeply nested destructuring patterns (e.g. [[[[...z...]]]]) recurse through bindValue without going through emitNode(), so the existing recursion guard there is bypassed and the process crashes with a native stack overflow. The fix mirrors the exact idiom already used in four places in BytecodeGenerator.h (emitNode, emitNodeInConditionContext, emitDefineClassElements): check VM::isSafeToRecurse(), and on failure call emitThrowExpressionTooDeepException() and return early.

Security risks

None introduced. The change is purely defensive — it converts an uncontrolled native stack overflow (SIGSEGV, potential DoS vector via crafted input) into a catchable RangeError. No new attack surface, no data handling, no auth/crypto.

Level of scrutiny

Low. This is a mechanical application of an established JSC pattern to two more recursion entry points. The added code is identical in shape to BytecodeGenerator.h:506-509 (void-returning variant). generator.vm() is a public VM& accessor already used ~15 times in the same file, and emitThrowExpressionTooDeepException() is public. The [[unlikely]] annotation matches existing usage. There is no happy-path behavior change: isSafeToRecurse() only trips near the soft stack limit, and shallow destructuring is unaffected.

Other factors

  • Early-returning from bindValue without emitting the destructuring bytecode is safe because emitThrowExpressionTooDeepException sets the generator's OOM flag, causing BytecodeGenerator::generate() to discard the CodeBlock and report ParserError::OutOfMemory — the partially-emitted bytecode is never executed.
  • The PR description documents manual verification across all destructuring entry points (let/const, for-of, catch, function params, eval).
  • The bug-hunting system found no issues.
  • 8 net lines added, no lines removed, single file — well within the threshold for a mechanical fix.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
ebad754e autobuild-preview-pr-355-ebad754e 2026-07-26 11:22:24 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant