diff --git a/src/shell_parser/braces.rs b/src/shell_parser/braces.rs index e9559592e45..447d423ca07 100644 --- a/src/shell_parser/braces.rs +++ b/src/shell_parser/braces.rs @@ -1370,7 +1370,12 @@ impl NewLexer { debug_assert!(matches!(first, Token::Open(_))); } - let mut braces: u8 = 0; + // PORT NOTE: Zig used `u8` here, which silently wraps in ReleaseFast. + // In Rust debug builds overflow-checks turn that wrap into a panic, so + // `{{{...}}}` deeper than 255 matched levels inside an unclosed outer + // brace aborted the process. Widened to `u32` to match the surrounding + // index type and also avoid the original latent wrap. + let mut braces: u32 = 0; self.replace_token_with_string(starting_idx); let mut i: u32 = starting_idx + 1; diff --git a/test/js/bun/shell/brace.test.ts b/test/js/bun/shell/brace.test.ts index 60cc524d181..9224a2eda30 100644 --- a/test/js/bun/shell/brace.test.ts +++ b/test/js/bun/shell/brace.test.ts @@ -1,5 +1,6 @@ import { $ } from "bun"; import { describe, expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; describe("$.braces", () => { test("no-op", () => { @@ -76,4 +77,23 @@ describe("$.braces", () => { const result = $.braces(`lol {😂,🫵,🤣}`); expect(result).toEqual(["lol 😂", "lol 🫵", "lol 🤣"]); }); + + test("unclosed outer with >255 matched nested pairs does not abort", async () => { + // Outer `{` is unmatched, but contains 300 levels of balanced `{...}` + // around an `x`. The rollback walk visits ≥300 consecutive `Open` tokens + // before the first `Close`, which used to overflow a `u8` counter and + // panic the debug build (released Bun silently wrapped). + const depth = 300; + const input = "{" + Buffer.alloc(depth, "{").toString() + "x" + Buffer.alloc(depth, "}").toString(); + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", `import { $ } from "bun"; $.braces(${JSON.stringify(input)});`], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout).toBe(""); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); + }); });