Skip to content

Bump WebKit (oven-sh/WebKit#700 preview): a literal nested past the parser's stack limit throws a RangeError - #43436

Draft
robobun wants to merge 1 commit into
mainfrom
robobun/fa2a54fe/jsc-parser-stack-overflow-ends-the-parse
Draft

robobun wants to merge 1 commit into
mainfrom
robobun/fa2a54fe/jsc-parser-stack-overflow-ends-the-parse

Conversation

@robobun

@robobun robobun commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Bun never returns from a program with an object or array literal nested past the JavaScriptCore parser's stack limit. (0, eval)("var x = " + "{v:".repeat(2900) + "1" + "}".repeat(2900)) spins one core and grows by about 250 MB per second. Node throws a RangeError in 44 ms.
  • A source file with the same literal does the same, and so does a macro that returns a deeply nested object (found on macros: fail the build on process.exit, Error returns, sparse arrays, and promises that never settle #40769).
  • The cause is in the WebKit fork. The parser treats "Stack exhausted" like a syntax error and parses the same text again as a destructuring pattern, at every nesting level on the way out (Source/JavaScriptCore/parser/Parser.cpp:4417).

Fix

  • [JSC] A parser stack overflow ends the parse WebKit#700 makes the first stack overflow end the parse. These programs now throw RangeError: Maximum call stack size exceeded. within milliseconds at any depth.
  • This PR pins the preview build of that branch: the current pin ebd5a6145bf7 plus that one commit.
  • It is a draft until [JSC] A parser stack overflow ends the parse WebKit#700 lands, because a preview release goes away when its PR closes. Then the pin moves to the merge commit's autobuild-<sha>.
  • Verified: new test/js/bun/jsc/parser-stack-overflow.test.ts. Both tests fail on the released Bun (the timeout kills the children) and pass with the pin. Also ran bun-jsc.test.ts and vm.test.ts.

Background

  • Bun links a prebuilt WebKit that scripts/build/deps/webkit.ts names. A WebKit pull request publishes a preview build as autobuild-preview-pr-<n>-<sha8>.
  • JavaScriptCore's parser is recursive descent with a stack check in each recursive function. It parses some text twice, because {a: b} can be a literal or a destructuring pattern.
  • A file that starts with // @bun skips Bun's transpiler. The module test uses that to reach JavaScriptCore's parser directly.
Notes

The limit is about 2900 levels of {v: on a release build of Bun.

History: oven-sh/WebKit#297 and #34339 had the same one-line parser change in July. #297 went stale with unrelated CI changes that conflict, and #34339 was closed in a stale-PR cleanup. The bug still reproduces on 1.4.3-canary.1+367d939d9. oven-sh/WebKit#700 replaces #297 and adds a stress test.

The test:

  • Each child has a 10 s timeout and the two tests do not run concurrently. Without the fix a child never exits and grows by about 250 MB per second, so this bounds a future regression to about 2.5 GB for 20 s. A child that the timeout killed shows in the assertion diff as exitCode: 143, signalCode: "SIGTERM".
  • With the fix each test takes about 0.5 s on a debug ASAN build.
  • The first child covers four shapes (object literal, array literal, array assignment pattern, arrow function parameter default), each through eval (the AST builder) and through new Function (the syntax checker). The WebKit PR has a stress test with more shapes.
  • Every program starts with var, because eval gives a program that is only a literal to LiteralParser, which never reaches this parser.

vm.test.ts on the debug build: one timing test (a SIGINT interrupts only the innermost of nested breakOnSigint runs) hit its 5 s default timeout on a machine with a load average near 300. Its sibling passed at 5.19 s. The other 301 tests pass.

What the WebKit change does to depth limits is in the tables of oven-sh/WebKit#700: a literal overflows at the same depth as before, and two rare kinds of program that recovered from an overflow no longer do.


[policy-decision:webkit] gate passed · iteration 0 · 2 files touched

passes on PR (with fix)
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/js/bun/jsc/parser-stack-overflow.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/js/bun/jsc/parser-stack-overflow.test.ts
bun test v1.4.3 (367d939d9)

test/js/bun/jsc/parser-stack-overflow.test.ts:
(pass) eval and the Function constructor throw a RangeError for nesting past the parser's stack limit [1352.62ms]
(pass) a module with a literal nested past the parser's stack limit fails to load with a RangeError [1281.62ms]

 2 pass
 0 fail
 2 expect() calls
Ran 2 tests across 1 file. [6.82s]
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                  |  2 +-
 test/js/bun/jsc/parser-stack-overflow.test.ts | 99 +++++++++++++++++++++++++++
 2 files changed, 100 insertions(+), 1 deletion(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                           reads  edits  tests
scripts/build/deps/webkit.ts                       1      1     17
test/js/bun/jsc/parser-stack-overflow.test.ts      1      4     16

JavaScriptCore's parser now ends the parse at the first stack overflow. A
literal nested past the parser's stack limit throws "RangeError: Maximum
call stack size exceeded." Before, the parser parsed the same text again as
a destructuring pattern at every nesting level on the way out and never
returned.

The preview build is the previous pin, ebd5a6145bf7, plus that one commit.
@robobun

robobun commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: draft. It waits for oven-sh/WebKit#700, which holds the fix. When that PR merges, the pin here must move from the preview tag to the merge commit's autobuild-<sha>.

How I reproduced it: (0, eval)("var x = " + "{v:".repeat(2900) + "1" + "}".repeat(2900)) never returns on bun 1.4.3-canary.1+367d939d9 (Linux x64). The prebuilt jsc shell of the pinned WebKit does the same, so no Bun code is involved. On that Bun, USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/parser-stack-overflow.test.ts fails both tests: each child spins until the timeout kills it. With the pin in this PR, bun bd test test/js/bun/jsc/parser-stack-overflow.test.ts passes both.

CI (build 118270): the new test passes on every lane. One job is red, debian 13 x64-asan, on test/js/bun/spawn/spawn.test.ts ("an idle reader stopped at the highwater"). That test also fails on main, and this PR does not touch it.

@robobun

robobun commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:56 AM PT - Sep 19th, 2026

❌ @robobun, your commit 8162eb8 has 1 failures in Build #118270 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 43436

That installs a local version of the PR into your bun-43436 executable, so you can run:

bun-43436 --bun

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant