Skip to content

test runner: store Scanner.fs as a raw pointer instead of casting away shared refs - #31850

Merged
Jarred-Sumner merged 6 commits into
mainfrom
claude/complex-19-test-runner-store-scanner-fs-as-a-raw-po
Jun 6, 2026
Merged

Jarred-Sumner merged 6 commits into
mainfrom
claude/complex-19-test-runner-store-scanner-fs-as-a-raw-po

Conversation

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

The bun test file-discovery Scanner held its FileSystem as a shared reference but derived &mut RealFS from it in two places (read_dir_with_name and next) via #[allow(invalid_reference_casting)] &T -> &mut T casts, giving the writes read-only provenance — undefined behavior under Rust's aliasing rules. The fix changes Scanner.fs to *mut FileSystem (the same shape Transpiler.fs already uses), storing transpiler.fs directly in Scanner::init. The two mutation sites now derive &mut (*self.fs).fs / &raw mut (*self.fs).fs with genuine write provenance, and the lint allow is gone. Reads outside the directory-iterator callback go through a new fs() accessor returning &'static FileSystem (sound: the singleton lives for the whole process); reads on the callback path (next and callees, which run while the resolver holds &mut to the singleton's fs field) use field-precise place projections (top_level_dir/filename_store/abs_buf_projected) so no whole-struct &FileSystem spans the mutably-borrowed field. The remaining reentrant Entry::kind *mut reborrow is the resolver's documented, entries_mutex-serialised contract and is called out in the SAFETY comments. The two scanner.fs.top_level_dir reads in test_command.rs were updated to use the accessor. New scanner-discovery tests in test/cli/test/bun-test.test.ts (deep nested scanning, dot-dir/node_modules pruning, subdirectory positional, single-file NotDir path) are behavior guards over the rewritten paths — the refactor has no intended behavior change, so they are not pre/post discriminators; verification is the build/test phase.

Verification

Implemented and verified on a unified integration branch: full debug build (linux-x64, ASAN), cargo check across the workspace, and the affected test files run against the debug build (failures cross-checked against main's build to exclude pre-existing issues). Each change was reviewed twice (compile/API correctness and GC/concurrency/semantics lenses) with findings repaired before landing.

@robobun

robobun commented Jun 4, 2026 •

Copy link
Copy Markdown
Collaborator

Comment thread src/runtime/cli/test/Scanner.rs Outdated
@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

Addressed in 6034828: fixed the Windows-only E0502 in the Scanner directory-descent loop (capture the &'static FileSystem before abs_buf_z borrows open_dir_buf), and corrected the matches_path_ignore_pattern comment — next() calls it directly; is_test_file is only on the scan() NotDir path.

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

Thanks for addressing the comment nit and the Windows borrow conflict. No further issues from me, but the core change here is a careful rework of unsafe aliasing/provenance invariants around the FileSystem singleton — worth a human sign-off on the soundness argument.

Extended reasoning...

Overview

The substantive change is confined to src/runtime/cli/test/Scanner.rs (plus two accessor call sites in test_command.rs and new behavior-guard tests in bun-test.test.ts): Scanner.fs changes from &'a FileSystem to *mut FileSystem, removing two #[allow(invalid_reference_casting)] &T → &mut T casts that gave writes read-only provenance. Reads are split into a whole-struct fs() accessor (off the callback path) and field-precise place projections top_level_dir()/filename_store()/abs_buf_projected() (on the callback path, where the resolver holds &mut to the singleton's .fs field). The remaining ~200 files are pure autofix.ci/rustfmt noise — trailing-newline removal, derive reflow, import reordering — with no semantic content.

Since my prior inline comment, commit 6034828 fixed both the Windows-only E0502 (by hoisting let fs = self.fs() above the abs_buf_z borrow) and the inaccurate next → is_test_file call-chain citation in the matches_path_ignore_pattern SAFETY comment.

Security risks

None. This is an internal soundness refactor of the test-file-discovery scanner; no parsing of untrusted input, no auth/crypto/permissions surface, no behavior change intended.

Level of scrutiny

High. The change is a strict improvement (it removes lint-silenced UB), but its correctness rests on a subtle, hand-argued aliasing model: which code paths run inside read_directory_with_iterator's &mut RealFS borrow, that field-precise raw-place projections of disjoint FileSystem fields don't conflict with that borrow, that abs_buf_projected is semantically equivalent to FileSystem::abs_buf (both call join_abs_string_buf::<platform::Loose>), and that the residual reentrant &raw mut (*self.fs).fs passed to Entry::kind while the outer &mut (*fs_ptr).fs is live is acceptable per the resolver's documented entries_mutex contract. The SAFETY comments are thorough and I found no flaws, but this is exactly the class of unsafe-Rust reasoning that benefits from a second human reader.

Other factors

The bug-hunting pass found nothing this round. New integration tests exercise deep-nested discovery, dot-dir/node_modules pruning, subdirectory positionals, and the single-file NotDir branch — good behavior guards over the rewritten paths. The robobun CI comment references the pre-fix commit (0872fe0); the Windows build break it reports was fixed in 6034828, and the bunx.test.ts failures are unrelated to the scanner. The autofix.ci formatting churn across 200 files is mechanical and inert.

Base automatically changed from claude/todo-audit-fixes to main June 5, 2026 03:30
@Jarred-Sumner
Jarred-Sumner requested a review from alii as a code owner June 5, 2026 03:30
Jarred-Sumner and others added 3 commits June 5, 2026 03:31
…y shared refs

The bun test file-discovery Scanner held its FileSystem as a shared reference but derived `&mut RealFS` from it in two places (`read_dir_with_name` and `next`) via `#[allow(invalid_reference_casting)]` `&T` -> `&mut T` casts, giving the writes read-only provenance — undefined behavior under Rust's aliasing rules. The fix changes `Scanner.fs` to `*mut FileSystem` (the same shape `Transpiler.fs` already uses), storing `transpiler.fs` directly in `Scanner::init`. The two mutation sites now derive `&mut (*self.fs).fs` / `&raw mut (*self.fs).fs` with genuine write provenance, and the lint allow is gone. Reads outside the directory-iterator callback go through a new `fs()` accessor returning `&'static FileSystem` (sound: the singleton lives for the whole process); reads on the callback path (`next` and callees, which run while the resolver holds `&mut` to the singleton's `fs` field) use field-precise place projections (`top_level_dir`/`filename_store`/`abs_buf_projected`) so no whole-struct `&FileSystem` spans the mutably-borrowed field. The remaining reentrant `Entry::kind` `*mut` reborrow is the resolver's documented, entries_mutex-serialised contract and is called out in the SAFETY comments. The two `scanner.fs.top_level_dir` reads in test_command.rs were updated to use the accessor. New scanner-discovery tests in test/cli/test/bun-test.test.ts (deep nested scanning, dot-dir/node_modules pruning, subdirectory positional, single-file NotDir path) are behavior guards over the rewritten paths — the refactor has no intended behavior change, so they are not pre/post discriminators; verification is the build/test phase.
Capture the &'static FileSystem before building path2 so the mutable
borrow of open_dir_buf doesn't conflict with a second self.fs() call
(E0502 on windows targets). Also correct the call-chain note on
matches_path_ignore_pattern: next() calls it directly.
@Jarred-Sumner
Jarred-Sumner force-pushed the claude/complex-19-test-runner-store-scanner-fs-as-a-raw-po branch from 6034828 to fdd60ec Compare June 5, 2026 03:31
@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Scanner struct refactors its filesystem field from a borrowed reference to a raw mutable pointer, introduces accessor helpers to mediate filesystem access, and updates all directory traversal, path computation, and filtering logic throughout the Scanner methods to use those helpers. TestCommand is updated to use the new accessor, and a comprehensive test suite validates scanner test discovery against nested directories, special directories, and individual file paths.

Changes

Scanner Filesystem Refactoring

Layer / File(s) Summary
Scanner field refactoring and accessor helpers
src/runtime/cli/test/Scanner.rs
Scanner stores filesystem as raw pointer *mut FileSystem instead of borrowed reference; import added for path-joining utilities; init updates to store raw pointer; new accessors (fs(), top_level_dir(), filename_store(), abs_buf_projected) introduced and integrated into scan().
Directory iteration and path computation
src/runtime/cli/test/Scanner.rs
read_dir_with_name derives mutable reference from raw pointer before directory reading; directory open, Windows traversal, and file discovery path computation updated to use fs() and abs_buf_projected helpers; directory name storage routed through accessors.
Iterator traversal and path filtering
src/runtime/cli/test/Scanner.rs
Iterator next() derives mutable filesystem reference from raw pointer; path-ignore filtering updated to compute relative paths via top_level_dir() helper; ignored directory pruning and file-path computation use abs_buf_projected; filename storage routed through accessor.
TestCommand integration and test discovery validation
src/runtime/cli/test_command.rs, test/cli/test/bun-test.test.ts
TestCommand updated to call scanner.fs() accessor before accessing top_level_dir; new concurrent test suite validates scanner discovers nested test files, prunes dot-directories and node_modules, and correctly handles single files and relative subdirectories.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and accurately summarizes the main change: converting Scanner.fs from a shared reference to a raw pointer to fix undefined behavior, which is the primary purpose of the PR.
Description check ✅ Passed The PR description comprehensively covers both required template sections: it clearly explains what the PR does (fixing undefined behavior by converting Scanner.fs to a raw pointer and refactoring borrows) and how verification was performed (full debug build, cargo check, and test execution).
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@Jarred-Sumner Jarred-Sumner left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Remove all these comments.

Comment thread test/cli/test/bun-test.test.ts Outdated
@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

Trimmed in fbe7b89 — cut the commentary from 75 lines to 27. What remains is the one-line SAFETY justification on each unsafe block plus a short doc line on fs() and the field projections explaining which accessor is legal on the iterator-callback path (that's the aliasing invariant this PR exists to enforce). Happy to strip those too if you want them gone entirely.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

Removed the remaining commentary (1 file changed, 4 insertions(+), 28 deletions(-)). Only one-line SAFETY notes on the unsafe blocks remain (required by the undocumented_unsafe_blocks lint).

@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: 1

🤖 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 `@src/runtime/cli/test/Scanner.rs`:
- Line 31: The public raw pointer field Scanner.fs should be made private so
external code cannot mutate it and break the new safety invariant; change the
declaration from a pub field to a non-public (or pub(crate)/module-private)
field and update callsites to use the accessor methods Scanner::fs(),
Scanner::top_level_dir(), and Scanner::filename_store() instead of directly
accessing Scanner.fs so all external access goes through the safe accessors.
🪄 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: 23df97d2-9d5e-4a3d-bffb-f735924b8c95

📥 Commits

Reviewing files that changed from the base of the PR and between 8553428 and f4eb2b9.

📒 Files selected for processing (3)
  • src/runtime/cli/test/Scanner.rs
  • src/runtime/cli/test_command.rs
  • test/cli/test/bun-test.test.ts

// `*mut FileSystem` (the shape `Transpiler.fs` already uses; init would
// store `transpiler.fs` directly) or RealFS needs interior mutability.
pub fs: &'a FileSystem,
pub fs: *mut FileSystem,

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Hide Scanner.fs behind the accessor.

Exposing the raw *mut FileSystem publicly makes the new safety invariant unenforceable. Any external write to Scanner.fs can invalidate the 'static references returned by fs(), top_level_dir(), and filename_store(). Now that callers have moved to scanner.fs(), this field should be private (or at least no wider than the module).

Suggested change
-    pub fs: *mut FileSystem,
+    fs: *mut FileSystem,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fs: *mut FileSystem,
fs: *mut FileSystem,
🤖 Prompt for 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.

In `@src/runtime/cli/test/Scanner.rs` at line 31, The public raw pointer field
Scanner.fs should be made private so external code cannot mutate it and break
the new safety invariant; change the declaration from a pub field to a
non-public (or pub(crate)/module-private) field and update callsites to use the
accessor methods Scanner::fs(), Scanner::top_level_dir(), and
Scanner::filename_store() instead of directly accessing Scanner.fs so all
external access goes through the safe accessors.

@Jarred-Sumner
Jarred-Sumner merged commit 300b148 into main Jun 6, 2026
73 of 78 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/complex-19-test-runner-store-scanner-fs-as-a-raw-po branch June 6, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants