Skip to content

glob: honour backslash escapes of non-special characters in scan() - #32876

Open
robobun wants to merge 5 commits into
mainfrom
farm/4badb7cf/glob-scan-backslash-escape
Open

glob: honour backslash escapes of non-special characters in scan()#32876
robobun wants to merge 5 commits into
mainfrom
farm/4badb7cf/glob-scan-backslash-escape

Conversation

@robobun

@robobun robobun commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Problem

Bun.Glob.scan() / scanSync() return nothing for a pattern with a backslash-escaped non-special character, while Bun.Glob.match() on the same pattern says it matches:

// files: "sp ace.txt", "st*ar.txt"
new Bun.Glob("sp\\ ace.txt").match("sp ace.txt");              // true
[...new Bun.Glob("sp\\ ace.txt").scanSync({ cwd })];           // []      (wrong)
[...new Bun.Glob("st\\*ar.txt").scanSync({ cwd })];            // ["st*ar.txt"]  (ok)

POSIX, bash and fast-glob all treat \x as quoting x whether or not x is a glob metacharacter, and Bun's own matcher already does the same. Only the scanner disagrees, so scan and match are inconsistent with each other.

Cause

GlobWalker::check_special_syntax in src/glob/GlobWalker.rs only looks for *[{?! when classifying a pattern component. A component like sp\ ace.txt contains none of those, so it is tagged SyntaxHint::Literal and routed through the literal fast paths: match_wildcard_literal (raw byte equality) and the statat() shortcut, both of which compare / open the filename with the backslash still in it. Escaping a special character (st\*ar.txt) happens to work because the * trips check_special_syntax, the component gets SyntaxHint::None, and matching falls through to the real matcher which unescapes correctly.

The *.ext fast path has the same blind spot: its bailout set is [{?*, so *.t\ s is tagged WildcardFilepath and compared as raw bytes.

Fix

Add \ to SYNTAX_TOKENS and to the WildcardFilepath bailout set, so any component that contains a backslash is classified as SyntaxHint::None and matched via the full matcher (glob::match), which already strips \ before any following character. On Windows \ is a path separator and is consumed before component classification, so this change is a no-op there.

Verification

Added describe("backslash escapes non-special characters") to test/js/bun/glob/scan.test.ts covering a literal component, a *.ext component, an escaped directory component, a **/ recursion, an absolute path, and a \\ negative control. On main 5 of the 6 cases fail (empty result); with this change all pass. Full scan.test.ts (178 tests) and match.test.ts (26 tests) remain green.

#32853 noted this divergence in its "not in this PR" section; this change addresses it.

Bun.Glob.scan() returned no results for patterns like 'sp\ ace.txt' even
though Bun.Glob.match() accepted them, because check_special_syntax did
not flag '\' and the component was routed through the Literal fast path
(raw memcmp / statat with the backslash still in the bytes). The same
applied to the WildcardFilepath '*.ext' fast path.

Add '\' to SYNTAX_TOKENS and to the WildcardFilepath bailout set so any
component containing a backslash falls through to the matcher, which
already unescapes '\x' to 'x' for any 'x'. On Windows '\' is a path
separator and never appears inside a component slice, so this is a no-op
there.
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 15 minutes and 58 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate 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 see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 84d5d294-732b-4f48-97d4-dda3c7ba582c

📥 Commits

Reviewing files that changed from the base of the PR and between 7c97cc2 and 1357bb5.

📒 Files selected for processing (1)
  • test/js/bun/glob/scan.test.ts

Walkthrough

On non-Windows, SYNTAX_TOKENS now includes the backslash character, causing check_special_syntax to recognize it. The WildcardFilepath fast-path optimization also aborts on backslash on non-Windows. A new test suite validates scan and match behavior for backslash-escaped filenames.

Backslash escape in glob

Layer / File(s) Summary
GlobWalker backslash escape logic
src/glob/GlobWalker.rs
SYNTAX_TOKENS is now conditional on IS_WINDOWS, adding \ on non-Windows. The WildcardFilepath fast-path scan disables the optimization when it encounters a backslash on non-Windows.
Backslash escape tests
test/js/bun/glob/scan.test.ts
Adds isWindows import and a describe.skipIf(isWindows) block that tests scanSync, async scan, glob.match, and absolute-path patterns against filenames containing backslash-escaped characters.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the affected glob scan behavior and the backslash-escape fix.
Description check ✅ Passed The description explains the problem, fix, and verification, though it uses different headings than the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@robobun

robobun commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 7:38 PM PT - Jun 27th, 2026

@robobun, your commit 1357bb5 has 1 failures in Build #65765 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 32876

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

bun-32876 --bun

Comment thread src/glob/GlobWalker.rs
Comment thread src/glob/GlobWalker.rs Outdated
robobun added 2 commits June 27, 2026 20:44
On Windows \ is a path separator, not an escape. A trailing \ on the
last component reaches check_special_syntax as part of comp_slice, so
including \ in SYNTAX_TOKENS there changed classification of patterns
like 'foo\' from Literal to None. Gate the \ additions on !IS_WINDOWS
so Windows behaviour is unchanged by this PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/bun/glob/scan.test.ts`:
- Around line 624-630: The shared temp fixture created in the glob scan test is
never cleaned up, leaving a real directory behind for the process lifetime. Add
teardown in the same test scope that sets up cwd with tempDirWithFiles(), using
an afterAll() cleanup or a disposable fixture pattern, so the glob scan tests
remain hermetic across repeated runs.
- Around line 633-647: The negative-control case in the Glob scan/match table is
not asserting the expected false outcome, so the suite never verifies
`glob.match()` for the escaped backslash pattern. Update the test in the
`test.each` block around `Glob`, `scanSync`, `scan`, and `match` so the `"sp\\\\
ace.txt"` case explicitly checks that `glob.match(...)` is false for the
non-matching candidate, while keeping the existing positive checks for the other
cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 70a094ee-ba7f-4167-92ac-d0334b5c56f1

📥 Commits

Reviewing files that changed from the base of the PR and between 8706328 and 7c97cc2.

📒 Files selected for processing (2)
  • src/glob/GlobWalker.rs
  • test/js/bun/glob/scan.test.ts

Comment thread test/js/bun/glob/scan.test.ts
Comment thread test/js/bun/glob/scan.test.ts

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — the !IS_WINDOWS gate in 7c97cc2 cleanly addresses the trailing-separator concern, and the POSIX fix is minimal and well-tested.

Extended reasoning...

Overview

This PR fixes a scan()/match() inconsistency in Bun.Glob: patterns with backslash-escaped non-special characters (e.g. sp\ ace.txt) matched via match() but returned nothing from scan(). The fix is 3 effective lines in src/glob/GlobWalker.rs: add \ to SYNTAX_TOKENS and to the WildcardFilepath bailout set, both gated on !IS_WINDOWS. This routes backslash-containing components through the full matcher (which already unescapes correctly) instead of the raw-byte-compare fast paths. A new describe block in scan.test.ts covers literal, *.ext, directory, **, absolute, and \\ negative-control cases.

Security risks

None. The change only affects which internal code path (fast byte compare vs. full glob matcher) handles a pattern component; it does not touch filesystem permissions, path resolution boundaries, or any auth/crypto surface. The fix strictly widens the set of components that go through the already-correct slow matcher — it can never cause a previously-slow-matched component to take a fast path, so the worst case is a tiny perf cost on \-containing patterns, not a correctness or traversal regression.

Level of scrutiny

Low–medium. This is a narrowly-scoped bug fix in pattern-component classification with a clear root-cause writeup and a conservative remedy. After commit 7c97cc2 gated both additions on !IS_WINDOWS, the Windows code path is byte-for-byte identical to main (SYNTAX_TOKENS is the same literal and the new match arm is dead), eliminating the trailing-\ edge case I flagged earlier. On POSIX the only behavioral change is for patterns that contain a backslash, which were already broken.

Other factors

Both of my prior inline comments are resolved: the pre-existing perf nit (loop over-scan) was acknowledged and reasonably deferred to a follow-up since it predates this PR and is correctness-neutral; the Windows trailing-separator concern was directly fixed by the IS_WINDOWS gate. The bug-hunting system found nothing on the latest revision. No CODEOWNERS entry covers src/glob/. Test coverage is solid (6 new cases exercising every classification path the fix touches, plus the existing 178 scan.test.ts and 26 match.test.ts tests reported green).

Remove the temp dir in afterAll and add an explicit match() assertion
for the escaped-backslash pattern so the negative control case actually
exercises match() instead of only the (empty) scan result.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — the !IS_WINDOWS gating addresses the trailing-separator concern and all other feedback is resolved.

Extended reasoning...

Overview

Two-line production change in src/glob/GlobWalker.rs: add \ to SYNTAX_TOKENS and to the WildcardFilepath bailout set, both gated on !IS_WINDOWS. This routes any pattern component containing a backslash through the full glob::match matcher (which already unescapes correctly) instead of the byte-equality Literal / WildcardFilepath fast paths, fixing the scan()/match() divergence for patterns like sp\ ace.txt. Accompanied by a 7-case test block in scan.test.ts covering literal, *.ext, directory, **, absolute-path, and a \\ negative control.

Security risks

None. No auth, crypto, permissions, or untrusted-input parsing is touched. The change is strictly conservative on the classifier side: it can only move components from a fast path to the slow path, never the reverse, so it cannot introduce a new way for a pattern to match something it shouldn't.

Level of scrutiny

Low-to-moderate. The diff is tiny and the mechanism is simple — widening a token set so more components fall through to the canonical matcher. On Windows the change is a literal no-op (both additions are if !IS_WINDOWS), which was the fix applied in 7c97cc2 after my earlier note about the trailing-separator edge case. On POSIX the only behavioural change is that backslash-containing components now match via glob::match instead of memcmp; correctness improves and the only cost is losing the fast path for the rare \-in-pattern case.

Other factors

  • All prior review threads (my two inline notes and the two CodeRabbit nits) are resolved; 7c97cc2 added the Windows gating and 1357bb5 added the afterAll cleanup plus the explicit negative-control match() assertion.
  • The bug-hunting system found nothing on the latest revision.
  • The two CI failures on 7c97cc2 (sql-mysql.auth.test.ts, v8-heap-snapshot.test.ts SIGKILL) are unrelated to glob.
  • No CODEOWNERS entry covers src/glob/ or the test file.
  • The new tests are POSIX-only (describe.skipIf(isWindows)), which is correct since \ is a path separator on Windows and the production change is gated out there.

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

Status: the change is complete and all review threads are resolved. The remaining CI red is infrastructure, not this diff.

On the latest run (build 65765, head 1357bb57), 283 jobs passed and all 3 failures are CI environment problems that never reached a glob test:

  • 2x darwin 26 aarch64 - test-bun: no tests ran at all.
    Error: buildkite-agent artifact download timed out after 120s for step 'darwin-aarch64-build-bun'.
    Refusing to continue with a partial download (would silently fall back to the wrong binary).
    
  • 1x darwin 14 aarch64 - test-bun: test/js/web/websocket/autobahn.test.ts, the bun-autobahn:local Docker image is linux/amd64 and the runner is arm64.
    The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)
    autobahn-1  | exec /opt/pypy/bin/wstest: exec format error
    

The two earlier runs failed on different but equally unrelated lanes: a sql-mysql.auth.test.ts Docker healthcheck timeout ("application not healthy after 1m0s") and a v8-heap-snapshot.test.ts SIGKILL on 25.04 x64. test/js/bun/glob/scan.test.ts has not failed on any lane in any of the three runs, and it is the only test file this PR touches.

Local verification on Linux x64 (debug, ASAN):

USE_SYSTEM_BUN=1 bun test test/js/bun/glob/scan.test.ts -t "backslash"   # 5 fail, 2 pass (stock 1.4.0)
bun bd test test/js/bun/glob/scan.test.ts -t "backslash"                 # 7 pass
bun bd test test/js/bun/glob/scan.test.ts                                # 178 pass (full file)
bun bd test test/js/bun/glob/match.test.ts                               # 26 pass

I've already pushed one empty retrigger for an earlier flake, so I'll stop re-rolling here to avoid spamming CI. Happy to rebase or re-push if a maintainer wants another run.

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