Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/shell_parser/braces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1370,7 +1370,12 @@ impl<const ENCODING: Encoding> NewLexer<ENCODING> {
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;
Expand Down
20 changes: 20 additions & 0 deletions test/js/bun/shell/brace.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { $ } from "bun";
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

describe("$.braces", () => {
test("no-op", () => {
Expand Down Expand Up @@ -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);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
});