Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 19 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,22 @@ 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 = "{" + "{".repeat(depth) + "x" + "}".repeat(depth);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", `import { $ } from "bun"; $.braces(${JSON.stringify(input)});`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(exitCode).toBe(0);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}, 30_000);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
});