Skip to content

refactor(rust): eliminate 57 borrowck-workaround allocs and unsafe launders - #35399

Open
robobun wants to merge 11 commits into
mainfrom
farm/796fd8f2/borrowck-bucket-B
Open

refactor(rust): eliminate 57 borrowck-workaround allocs and unsafe launders#35399
robobun wants to merge 11 commits into
mainfrom
farm/796fd8f2/borrowck-bucket-B

Conversation

@robobun

@robobun robobun commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Bucket B of the reshaped for borrowck cleanup: 57 sites with simple local fixes (<10 lines each, no signature changes needed except one vestigial &mut, no new fields). Brings the count from 368 to 311.

What changed

Entry-API double-lookups (6 sites): get() + insert() collapsed into a single entry()/get_or_put() where the map API supports it (fold.rs both arms, snapshot.rs). Three sites (WorkspacePackageJSONCache, SavedSourceMap, runTasks:544) keep the two-step shape because an entry borrow cannot span the intervening read/parse/directory call; comment updated to say why.

Allocation drops (31 sites):

  • &[Chunk] instead of &mut [Chunk] for append_isolated_hashes_for_imported_chunks (read-only recursion): LinkerContext both loops become plain iteration
  • index + re-borrow per iteration instead of .collect(): computeChunks, yarn, PackageManagerEnqueue:111
  • mem::take + restore instead of .clone()/.to_vec(): computeCrossChunkDependencies, repl::handle_enter, IOReader both callbacks, server_body
  • split_at_mut instead of prefix .to_vec(): braces.rs ×3
  • plain &[u8] where the borrow was already disjoint: install_with_manager, patchPackage ×2, bake/production ×2, repl::handle_tab/arrow-keys, bundle_v2:2440
  • SmallVec where a snapshot is required but tiny: h2_frame_parser, IOWriter
  • inline the value instead of stashing in an owned local: defines.rs, js_parser/lib.rs, dotenv/env_loader, seq.rs (also deletes the now-dead Seq::buf field), diff_match_patch
  • BackRef instead of .cloned(): visit_stmt.rs (matches the existing pattern in visit/mod.rs)

Three sites keep their allocation because it is required for correctness, with the comment rewritten to explain the hazard instead of reshaped for borrowck:

  • css/properties/flex.rs:879: basis is emitted twice (shorthand + longhand)
  • PackageManagerEnqueue.rs:995: string_bytes may reallocate under get_or_put_resolved_package_with_find_result
  • filesystem_router.rs bust_dir_cache: the recursive walk races the bundler thread on the process-global entry cache (the reload() while Bun.build() test SIGSEGV'd on aarch64 without the copy)

Raw-pointer launders rewritten to safe Rust (20 sites):

  • statement reorder so borrows don't overlap: BunObject, publish_command, VirtualMachine:4313
  • disjoint field borrows: VirtualMachine:5527, PostgresSQLConnection:2407, MySQLConnection:1705, PostgresSQLConnection:1743
  • scopeguard::guard(payload, ..) instead of sibling *mut copy: js_bun_spawn_bindings ×2, jsc_hooks
  • usize address-diff for offset math, derive the final pointer from the live slice: resolve_path ×2
  • match &mut req instead of from_ref().cast_mut(): server/mod
  • SmallVec<[u8; 64]> copy instead of detach_lifetime on a package name: runTasks:347
  • two-step get/put instead of holding a *mut u32 across &mut self: bundle_v2:2135
  • safe loop-invariant hoist instead of RawSlice: AsyncModule
  • borrow from the live self.store() instead of a *const [u8] round-trip: Blob.rs
  • local-slice reuse instead of StoreStr arena alloc: lexer.rs
  • comment-only (code was already idiomatic): interpreter.rs, ConsoleObject.rs

Dead code: deleting the unused destination_dir binding in PackageInstaller.rs made LazyPackageDestinationDir dead (the remaining caller only ever constructs ::Dir(fd) and get_dir() is infallible for it). The enum and its impl are removed and the single use site reads destination_dir.fd() directly.

Already done on main: KEventWatcher.rs:130 and WindowsWatcher.rs:525 were handled by #35321.

Test: test/internal/source-lints/borrowck-reshape-markers.test.ts pins the marker count at 311 so it only moves down.

Why

Every site was code that paid a runtime cost (an extra heap allocation, a double hash lookup, or an unsafe block) purely to placate the borrow checker at porting time. Each prescribed rewrite here is the idiomatic-Rust shape the code would have been written in directly: disjoint borrows the compiler can already prove, or moving a value instead of cloning it. Net: the hot-path per-iteration allocations from the audit are gone, 20 unsafe launders are replaced with safe equivalents, and the marker comments are removed.

Verification

  • cargo check --workspace and bun run rust:check-all (all 10 targets) clean
  • cargo clippy on every touched crate clean
  • bun bd builds
  • rg "reshaped for borrowck" --type rust | wc -l: 368 → 311
  • Test suites for touched areas pass with no new failures vs main: brace.test.ts, seq.test.ts, filesystem_router.test.ts, repl.test.ts, node/path, console-table.test.ts, env.test.ts, bun-patch.test.ts, snapshot tests, bundler edgecase/minify/splitting/html/cjs2esm, shell suite

[review] gate passed · iteration 9 · 40 files touched

fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/borrowck-reshape-markers.test.ts
bun test v1.4.0 (c411f3108)

test/internal/source-lints/borrowck-reshape-markers.test.ts:
47 |     throw new Error(
48 |       `Found ${count} 'reshaped for borrowck' markers in src/**/*.rs, up from ${LIMIT}.\n` +
49 |         `Prefer the idiomatic Rust form over adding a new workaround; if unavoidable, bump LIMIT in this file.\n` +
50 |         `First ${sample.length}:\n` +
51 |         sample.map(l => `  ${l}`).join("\n"),
52 |     );
         ^
error: Found 342 'reshaped for borrowck' markers in src/**/*.rs, up from 293.
Prefer the idiomatic Rust form over adding a new workaround; if unavoidable, bump LIMIT in this file.
First 20:
  src/bundler/bundle_v2.rs:1132
  src/bundler/bundle_v2.rs:1265
  src/bundler/bundle_v2.rs:1892
  src/bundler/bundle_v2.rs:1901
  src/bundler/bundle_v2.rs:1963
  src/bundler/bundle_v2.rs:1968
  src/bundler/bundle_v2.rs:1999
  src/bundler/bundle_v2.rs:2070
  src/bundler/bundle_v2.rs:2115
  src/bundler/bundle_v2.rs:2135
  src/bundler/bundle_v2.rs:22
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (1498d7b77)

test/internal/source-lints/borrowck-reshape-markers.test.ts:
47 |     throw new Error(
48 |       `Found ${count} 'reshaped for borrowck' markers in src/**/*.rs, up from ${LIMIT}.\n` +
49 |         `Prefer the idiomatic Rust form over adding a new workaround; if unavoidable, bump LIMIT in this file.\n` +
50 |         `First ${sample.length}:\n` +
51 |         sample.map(l => `  ${l}`).join("\n"),
52 |     );
         ^
error: Found 342 'reshaped for borrowck' markers in src/**/*.rs, up from 293.
Prefer the idiomatic Rust form over adding a new workaround; if unavoidable, bump LIMIT in this file.
First 20:
  src/bundler/bundle_v2.rs:1132
  src/bundler/bundle_v2.rs:1265
  src/bundler/bundle_v2.rs:1892
  src/bundler/bundle_v2.rs:1901
  src/bundler/bundle_v2.rs:1963
  src/bundler/bundle_v2.rs:1968
  src/bundler/bundle_v2.rs:1999
  src/bundler/bundle_v2.rs:2070
  src/bundler/bundle_v2.rs:2115
  src/bundler/bundle_v2.rs:2135
  src/bundler/bundle_v2.rs:2249
  src/bundler/bundle_v2.rs:2440
  src/bundler/bundle_v2.rs:6455
  src/bundler/Chunk.rs:306
  src/bundler/entry_points.rs:158
  src/bundler/entry_points.rs:195
  src/bundler/linker_c
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/borrowck-reshape-markers.test.ts
bun test v1.4.0 (c411f3108)

test/internal/source-lints/borrowck-reshape-markers.test.ts:
(pass) 'reshaped for borrowck' markers are at or below the ratchet (293) [11.31ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [84.34s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     c411f31081
  features     baseline

22 deps, 108 codegen, 1170 objects in 3739ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1233] install /workspace/bun
bun install v1.4.0-canary.1 (1498d7b77)

Checked 124 installs across 170 packages (no changes) [85.00ms]
[2/1233] gen ErrorCode+*.h
[3/1233] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConstants.cpp
[4/1233] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (1498d7b77)

Checked 1 install across 2 packages (no changes) [2.00ms]
[5/1233] fetch zlib
[zlib] up to date
[6/1233] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (1498d7b77)

Checked 129 installs across 147 packages (no changes) [21.00ms]
[7/1233] gen bindgenv2
[8/1233] fetch picohttpparser
[picohttpparser] up to date
[9/1233] fetch tinycc
[tinycc] up to date
[10/1233] subst deps/zlib/zconf.h
[11/1233] f
... (truncated)
diff hotspot
src/bundler/LinkerContext.rs                       | 122 +++++++++------------
 src/bundler/bundle_v2.rs                           |  45 +++-----
 src/bundler/linker_context/computeChunks.rs        |  22 ++--
 .../computeCrossChunkDependencies.rs               |   7 +-
 src/css/properties/flex.rs                         |   2 +-
 src/dotenv/env_loader.rs                           |   6 +-
 src/install/PackageInstaller.rs                    |  85 +-------------
 .../PackageManager/PackageManagerEnqueue.rs        |   8 --
 .../PackageManager/WorkspacePackageJSONCache.rs    |   6 +-
 src/install/PackageManager/install_with_manager.rs |   6 +-
 src/install/PackageManager/patchPackage.rs         |  15 +--
 src/install/PackageManager/runTasks.rs             |  16 +--
 src/js_parser/fold.rs                              |  65 +++++------
 src/js_parser/lexer.rs                             |  13 +--
 src/js_parser/visit/visit_stmt.rs                  |  14 ++-
 src/jsc/AsyncModule.rs                             |  15 ++-
 src/jsc/ConsoleObject.rs                           |   2 -
 src/jsc/SavedSourceMap.rs                          |   4 -
 src/jsc/VirtualMachine.rs                          |  16 +--
 src/paths/resolve_path.rs                          |  20 +---
 src/runtime/api/BunObject.rs                       |  15 +--
 src/runtime/api/bun/h2_frame_parser.rs             |   5 +-
 src/runtime/api/bun/js_bun_spawn_bindings.rs       |  28 ++---
 src/runtime/api/filesystem_router.rs               |  10 +-
 src/runtime/bake/production.rs                     |   9 +-
 src/runtime/cli/publish_command.rs                 |  22 ++--
 src/runtime/cli/repl.rs                            |  19 ++--
 src/runtime/jsc_hooks.rs                           |  61 +++--------
 src/runtime/server/mod.rs                          |  10 +-
 src/runtime/server/server_body.rs                  |  10 +-
 src/runtime/shell/IOReader.rs                      |   5 +-
 src/runtime/shell/IOWr
... (truncated)

gate history · 1 passed · 4 rejected · iteration 9

evidence per changed file
file                                                      reads  edits  tests
src/bundler/LinkerContext.rs                                  3      2      0
src/bundler/bundle_v2.rs                                      1      2      0
src/bundler/linker_context/computeChunks.rs                   1      0      0
…bundler/linker_context/computeCrossChunkDependencies.rs      3      0      0
src/css/properties/flex.rs                                    0      0      0
src/dotenv/env_loader.rs                                      1      0      0
src/install/PackageInstaller.rs                               3      3      0
src/install/PackageManager/PackageManagerEnqueue.rs           2      2      0
src/install/PackageManager/WorkspacePackageJSONCache.rs       0      0      0
src/install/PackageManager/install_with_manager.rs            0      0      0
src/install/PackageManager/patchPackage.rs                    0      0      0
src/install/PackageManager/runTasks.rs                        1      1      0
src/js_parser/fold.rs                                         1      1      0
src/js_parser/lexer.rs                                        2      2      0
src/js_parser/visit/visit_stmt.rs                             0      0      0
src/jsc/AsyncModule.rs                                        0      0      0
(+ 24 more files)

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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

How do review 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 refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bbb46b28-b202-40ba-9efc-eb2578dc802e

📥 Commits

Reviewing files that changed from the base of the PR and between e004aaa and d6ba924.

📒 Files selected for processing (40)
  • src/bundler/LinkerContext.rs
  • src/bundler/bundle_v2.rs
  • src/bundler/linker_context/computeChunks.rs
  • src/bundler/linker_context/computeCrossChunkDependencies.rs
  • src/css/properties/flex.rs
  • src/dotenv/env_loader.rs
  • src/install/PackageInstaller.rs
  • src/install/PackageManager/PackageManagerEnqueue.rs
  • src/install/PackageManager/WorkspacePackageJSONCache.rs
  • src/install/PackageManager/install_with_manager.rs
  • src/install/PackageManager/patchPackage.rs
  • src/install/PackageManager/runTasks.rs
  • src/js_parser/fold.rs
  • src/js_parser/lexer.rs
  • src/js_parser/visit/visit_stmt.rs
  • src/jsc/AsyncModule.rs
  • src/jsc/ConsoleObject.rs
  • src/jsc/SavedSourceMap.rs
  • src/jsc/VirtualMachine.rs
  • src/paths/resolve_path.rs
  • src/runtime/api/BunObject.rs
  • src/runtime/api/bun/h2_frame_parser.rs
  • src/runtime/api/bun/js_bun_spawn_bindings.rs
  • src/runtime/api/filesystem_router.rs
  • src/runtime/bake/production.rs
  • src/runtime/cli/publish_command.rs
  • src/runtime/cli/repl.rs
  • src/runtime/jsc_hooks.rs
  • src/runtime/server/mod.rs
  • src/runtime/server/server_body.rs
  • src/runtime/shell/IOReader.rs
  • src/runtime/shell/IOWriter.rs
  • src/runtime/shell/interpreter.rs
  • src/runtime/test_runner/diff/diff_match_patch.rs
  • src/runtime/test_runner/snapshot.rs
  • src/runtime/webcore/Blob.rs
  • src/sql_jsc/mysql/MySQLConnection.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • test/internal/source-lints/borrowck-reshape-markers.test.ts
  • test/internal/source-lints/dead-code-escape-limits.json

Walkthrough

This PR removes borrow-checker workarounds across bundler, package installation, parser, runtime, shell, SQL, and test code. It replaces temporary allocations, raw-pointer lifetime shaping, cloned collections, and lazy state with direct borrowing, ownership transfer, scoped guards, and inline collections.

Changes

Borrow-checker cleanup

Layer / File(s) Summary
Bundler graph and hash traversal
src/bundler/...
Chunk hashing, source-index resolution, entry-bit handling, define insertion, and cross-chunk dependency processing now use direct access patterns.
Package installation and dependency handling
src/install/...
Package installation removes lazy directory state, adds alias validation, and simplifies dependency, manifest, patch, error-formatting, and Yarn graph handling.
Parser and JSC ownership paths
src/js_parser/..., src/jsc/...
Parser and JSC paths replace cloned values, temporary collections, and pointer-based lifetime workarounds.
Runtime, server, and shell lifetimes
src/runtime/...
Runtime, server, shell, REPL, process cleanup, and file-descriptor paths use direct reborrows, moved state, scoped guards, or inline collections.
Buffer, SQL, and source-lint updates
src/css/..., src/dotenv/..., src/paths/..., src/sql_jsc/..., src/shell_parser/..., test/internal/...
Buffer, CSS, SQL, S3, diff, snapshot, brace-expansion, environment, and source-lint paths reduce ownership reshaping and update marker validation.

Possibly related PRs

  • oven-sh/bun#35370: Shares related borrow-checker rewrites in defines, Yarn handling, brace expansion, and the shell sequence builtin.
  • oven-sh/bun#35392: Modifies the same peer-dependency skip logic in PackageManagerEnqueue.rs.
  • oven-sh/bun#33072: Also changes the install-target alias helper in PackageInstaller.rs.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title accurately summarizes the main change: removing borrowck workaround allocations and unsafe launders.
Description check ✅ Passed The description covers what changed and how it was verified, even though it doesn't use the template's exact headings.

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

@robobun

robobun commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 1:32 AM PT - Jul 25th, 2026

@robobun, your commit d6ba924 has 1 failures in Build #80075 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 35399

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

bun-35399 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. unsafe: raw-pointer borrowck workaround — should add compiler-checkable invariants #30767 - This PR directly eliminates raw-pointer borrowck workarounds with safe Rust patterns, which is exactly what this issue tracks
  2. Rust port regressions #31477 - This PR reduces "reshaped for borrowck" markers from 368 to 311 and removes 57 unnecessary Zig-to-Rust port artifacts, directly improving port quality

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #30767
Fixes #31477

🤖 Generated with Claude Code

@robobun

robobun commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

This is a behavior-preserving refactor (bucket B of the reshaped for borrowck cleanup): every site does the same thing before and after, with fewer allocations / hash lookups / unsafe blocks. Coverage is the type checker plus the existing test suites for every touched area, all of which pass with no new failures vs main (see the Verification section in the PR body).

Status at d6ba924 (synced with main at ae4b17d):

CI: build #79970 had 189 lanes pass with zero test failures (7 expired agents). The retrigger (#80075) hit a fleet-wide build-job timeout (2 build jobs timed out, 169 downstream never ran). The diff itself is green; ready to merge.

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

I didn't find any bugs, but this touches 43 files across bundler/parser/install/JSC/SQL/shell with subtle borrow-lifetime rewrites, so it's worth a human pass.

What was reviewed:

  • mem::take-then-restore sites checked for early-return leaks — the ? at computeCrossChunkDependencies.rs:366 fires only on OOM into a fn-local chunk_metas, so the unrestored imports is unobservable.
  • server_body::load_and_resolve_plugins now leaves self.state = Err (instead of Unqueued) if to_js_array/create_utf8_for_js ?-return before line 1016 — likely intentional but a state-machine change.
  • append_isolated_hashes_for_imported_chunks &mut& signature change: the external caller in generateChunksInParallel.rs:351 still passes &mut which auto-reborrows.
  • seq.rs reorder (enqueue before storing buf), repl::handle_enter buffer take, fold.rs get_or_put index write, LazyPackageDestinationDir deletion — all traced, no issues found.
Extended reasoning...

Overview

Bucket-B cleanup of "reshaped for borrowck" markers: 57 sites across 43 Rust files, replacing porting-era workarounds (extra allocations, double hash lookups, unsafe pointer launders) with idiomatic safe Rust. Categories: entry-API collapses, .to_vec()/.clone()mem::take+restore or split_at_mut, &mut& where recursion is read-only, scopeguard::guard(payload) instead of sibling raw-pointer copies, and dead-code deletion (LazyPackageDestinationDir).

Security risks

None identified. No parsing of untrusted input changed, no auth/crypto/permissions touched. The unsafe removals reduce attack surface rather than adding it.

Level of scrutiny

High. Each individual site is small and mechanical, but the aggregate spans hot paths in the bundler linker, JS parser fold pass, package installer, VM lifecycle, HTTP/2 frame parser, SQL connection readers, and shell IO. Several rewrites (mem::take+restore, state mem::replace, statement reordering around enqueue) change error-path semantics in ways that only matter under failure — exactly the class the repo review guide calls out ("Every error/abort path actively completes the operation"). The server_body state change and computeCrossChunkDependencies take-without-guard are examples where I convinced myself they're fine, but a maintainer familiar with each subsystem's invariants should confirm.

Other factors

  • No new tests (behavior-preserving refactor by design; PR body argues type checker + existing suites are the coverage).
  • CI build #79473 was still running at review time.
  • No prior human review comments.
  • The self-review commit (1e5e583d) already addressed one round of issues (twin sites, &[Chunk] signature).
  • The fold.rs get_or_put rewrite leaves an uninitialized value slot live across p.new_symbol()/p.module_scope_mut() before writing values_mut()[index] — I verified those calls don't touch commonjs_named_exports, but this is the kind of implicit invariant a maintainer should sanity-check.

@robobun

robobun commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

On the server_body::load_and_resolve_plugins state observation: the ? returns at lines 1013/1014 only surface as JsError::Thrown or JsError::OutOfMemory, and the sole caller (get_or_load_plugins, line 1402) panics on Thrown/Terminated and aborts on OutOfMemory. So whether self.state is Err or Unqueued at that point is unobservable. The mem::replace(.., Err) placeholder also matches the existing pattern at lines 1090 and 1130 in the same file.

On fold.rs get_or_put: array_hash_map::get_or_put writes V::default() on miss (src/collections/array_hash_map.rs:1191), so the slot is a valid CommonJSNamedExport::default(), not uninitialized, while p.new_symbol/p.module_scope_mut() run. Neither touches commonjs_named_exports.

Comment thread src/runtime/shell/builtin/seq.rs Outdated
@robobun
robobun force-pushed the farm/796fd8f2/borrowck-bucket-B branch from b22ac04 to 0888f68 Compare July 24, 2026 09:34
Comment thread src/runtime/api/filesystem_router.rs

@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 `@test/internal/source-lints/borrowck-reshape-markers.test.ts`:
- Line 22: Update the borrowck marker ratchet in the test to match the actual
repository-wide count of 330, or complete the cleanup so the count genuinely
reaches the intended lower threshold. Verify the result using the test’s
source-lint counting logic before finalizing, and keep the assertion in the
borrowck reshape marker test consistent with the current marker total.
🪄 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: a0ca34b2-53c4-4eb7-8d5a-cc0789808f6a

📥 Commits

Reviewing files that changed from the base of the PR and between df84f8d and 125e883.

📒 Files selected for processing (45)
  • src/bundler/LinkerContext.rs
  • src/bundler/bundle_v2.rs
  • src/bundler/defines.rs
  • src/bundler/linker_context/computeChunks.rs
  • src/bundler/linker_context/computeCrossChunkDependencies.rs
  • src/css/properties/flex.rs
  • src/dotenv/env_loader.rs
  • src/install/PackageInstaller.rs
  • src/install/PackageManager/PackageManagerEnqueue.rs
  • src/install/PackageManager/WorkspacePackageJSONCache.rs
  • src/install/PackageManager/install_with_manager.rs
  • src/install/PackageManager/patchPackage.rs
  • src/install/PackageManager/runTasks.rs
  • src/install/yarn.rs
  • src/js_parser/fold.rs
  • src/js_parser/lexer.rs
  • src/js_parser/lib.rs
  • src/js_parser/visit/visit_stmt.rs
  • src/jsc/AsyncModule.rs
  • src/jsc/ConsoleObject.rs
  • src/jsc/SavedSourceMap.rs
  • src/jsc/VirtualMachine.rs
  • src/paths/resolve_path.rs
  • src/runtime/api/BunObject.rs
  • src/runtime/api/bun/h2_frame_parser.rs
  • src/runtime/api/bun/js_bun_spawn_bindings.rs
  • src/runtime/api/filesystem_router.rs
  • src/runtime/bake/production.rs
  • src/runtime/cli/publish_command.rs
  • src/runtime/cli/repl.rs
  • src/runtime/jsc_hooks.rs
  • src/runtime/server/mod.rs
  • src/runtime/server/server_body.rs
  • src/runtime/shell/IOReader.rs
  • src/runtime/shell/IOWriter.rs
  • src/runtime/shell/builtin/seq.rs
  • src/runtime/shell/interpreter.rs
  • src/runtime/test_runner/diff/diff_match_patch.rs
  • src/runtime/test_runner/snapshot.rs
  • src/runtime/webcore/Blob.rs
  • src/shell_parser/braces.rs
  • src/sql_jsc/mysql/MySQLConnection.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • test/internal/source-lints/borrowck-reshape-markers.test.ts
  • test/internal/source-lints/dead-code-escape-limits.json
💤 Files with no reviewable changes (4)
  • test/internal/source-lints/dead-code-escape-limits.json
  • src/jsc/SavedSourceMap.rs
  • src/runtime/shell/interpreter.rs
  • src/jsc/ConsoleObject.rs

Comment thread test/internal/source-lints/borrowck-reshape-markers.test.ts Outdated

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

No new issues found on e004aaa — the two earlier findings (dead Seq::buf field; filesystem_router aarch64 segfault) are both addressed. That said, this is a 45-file refactor rewriting aliasing-sensitive code across the bundler, parser, JSC (VirtualMachine, Blob), package manager, shell, and SQL connection paths, and one earlier revision already segfaulted in CI, so a human pass is warranted.

What was reviewed:

  • braces.rs split_at_mut: verified new_key > out_key invariant holds (counter is monotone, out_key always a prior counter value).
  • patchPackage.rs / install_with_manager.rs dropped .to_vec(): lockfile is a local disjoint from manager.lockfile; borrowck proves the rest.
  • computeCrossChunkDependencies.rs mem::take: imports is restored after the loop; nothing inside reads chunk_metas[chunk_index].imports.
  • LinkerContext::append_isolated_hashes_for_imported_chunks: checked the two callers — both already pass &*chunks/immutable, so the &mut → & signature change is compatible.
Extended reasoning...

Overview

Bucket B of the reshaped for borrowck cleanup: 57 sites across 45 files, replacing per-iteration allocations, double hash lookups, and raw-pointer lifetime launders with idiomatic Rust (entry API, mem::take, split_at_mut, disjoint field borrows, scopeguard::guard payloads, index+reborrow). Also deletes the now-dead LazyPackageDestinationDir enum from PackageInstaller.rs and adds a ratchet test pinning the marker count at 311.

Security risks

None identified. No auth/crypto/permission surface. The changes are internal borrow-shape rewrites; the closest to security-adjacent is the SQL connection read() refactor (MySQL/Postgres), which reindexes into byte_list after adjusting head — bounds are still checked against remaining().len() before the read.

Level of scrutiny

High. Per REVIEW.md, memory safety is the most-blocked category, and this PR is entirely about rewriting borrow/lifetime shapes in native code — including 20 unsafe launders replaced with safe equivalents, and several sites where the original comment explicitly documented an aliasing hazard (e.g. patchPackage.rs, Blob.rs, jsc_hooks.rs, VirtualMachine.rs). The type checker and existing test suites are the primary coverage, but an earlier revision of this same PR segfaulted on aarch64 (filesystem_router) despite the local suite passing — a concrete demonstration that borrowck acceptance ≠ runtime soundness when UnsafeCell/BackRef/interior mutability are involved.

Other factors

  • Both prior findings from this reviewer are addressed and resolved (0888f68).
  • The CodeRabbit ratchet-count finding was a symlink double-count, fixed in e004aaa.
  • CI on the latest commit (e004aaa, build #79543) is still building per the robobun status comment.
  • Several changes lean on "it compiles so borrowck proved it" for sites whose original comment named an aliasing hazard through interior mutability (JsCell, BackRef, raw *mut). Those are precisely the cases borrowck cannot see, and warrant a human check that the specific hazard the original author documented is genuinely absent — particularly Blob.rs (store borrowed from self across heap::into_raw(t)), repl::handle_enter (mem::take of the line buffer vs. the previous copy-then-clear), and the lexer.rs scan_pragma reborrow.

let import_refs: Vec<Ref> = chunk_metas[chunk_index].imports.keys().to_vec();
for import_ref in import_refs {
let imports = core::mem::take(&mut chunk_metas[chunk_index].imports);
for &import_ref in imports.keys() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is all run on one thread right? I don't remember

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.

Yes, single-threaded. chunk_metas is a fn-local Vec<ChunkMeta> created at line 24 of compute_cross_chunk_dependencies, filled by the sequential for loop at line 78-80, then handed to this function as &mut [ChunkMeta]; it never leaves the call stack.

The walk step that esbuild runs per-chunk in goroutines is a plain sequential for chunk in chunks.iter_mut() here, so nothing else can observe chunk_metas[chunk_index].imports being empty between the mem::take and the restore at line 386. The body itself only touches chunk_metas[other_chunk_index].exports (guarded other_chunk_index != chunk_index at line 344) and chunks[chunk_index], never chunk_metas[*].imports.

@dylan-conway

Copy link
Copy Markdown
Member

@robobun sync!

robobun and others added 10 commits July 25, 2026 01:38
…unders

Removes 57 'reshaped for borrowck' sites where the Zig->Rust port
introduced extra heap allocations, double hash lookups, or unsafe
raw-pointer launders to satisfy the borrow checker. Each fix is a local
restructure (no signature changes, no new fields) that preserves
behavior exactly.

Breakdown:

- 6 entry-API sites: collapse get()+insert() double-lookup into a
  single entry()/get_or_put() lookup (fold.rs, snapshot.rs,
  ConsoleObject.rs; three kept as-is with the NLL/parse-borrow note
  since the entry API doesn't fit).
- 31 allocation sites: drop the extra Vec/Box via index+reborrow
  (LinkerContext, computeChunks, yarn), mem::take+restore
  (computeCrossChunkDependencies, repl handle_enter, IOReader,
  server_body), split_at_mut (braces x3), plain &[u8] where the borrow
  was already disjoint (filesystem_router, patchPackage,
  install_with_manager, bake/production x2), or SmallVec where a
  snapshot must stay (h2_frame_parser, IOWriter). Two sites
  (flex.rs:879, PackageManagerEnqueue.rs:995) keep the allocation
  because the underlying buffer reallocates or is emitted twice; their
  comment now explains that instead of 'reshaped for borrowck'.
- 20 raw-pointer launders rewritten to safe Rust: reorder statements
  (BunObject, publish_command), disjoint field borrows
  (VirtualMachine:5527, PostgresSQLConnection:2407), scopeguard payload
  instead of sibling *mut copy (js_bun_spawn_bindings x2, jsc_hooks),
  usize offset math instead of *mut base-ptr (resolve_path x2), match
  &mut instead of from_ref().cast_mut() (server/mod), SmallVec copy
  instead of detach_lifetime (runTasks), and simple comment deletion
  where the hoist was already idiomatic (interpreter).

PackageInstaller.rs: the else-branch destination_dir that was only ever
close()'d (a no-op) is deleted, which made LazyPackageDestinationDir
dead; removed the enum and the now-unreachable get_dir() error path.

368 -> 311 'reshaped for borrowck' comments remain (-57).
43 files, +247 / -517 lines.

Two sites from the original audit were already fixed on main by #35321
(KEventWatcher.rs:130, WindowsWatcher.rs:525).
…ed_hashes

- IOReader: on_reader_done_cb now mem::takes s.readers like on_reader_error
  (both are terminal; dispatch_reader_done creates a fresh IOReader, never
  re-registers on this one).
- fold.rs: apply the same get_or_put collapse to the exports_ref arm at :384
  so both commonjs_named_exports sites use one hash lookup.
- LinkerContext: append_isolated_hashes_for_imported_chunks only reads chunks,
  so take &[Chunk]. Both loops become plain iteration with no per-step
  re-indexing and no unreachable!(). The sole caller auto-reborrows.
Pins the current marker count so it only moves down as the remaining
cleanup buckets land. Fails if a new workaround is introduced without
lowering the limit.
…dead Seq::buf

filesystem_router: restore the dir copy in bust_dir_cache; CI on
ubuntu-aarch64 hit a SIGSEGV in the reload()/Bun.build() race test with
the borrowed slice. The recursive walk races the bundler thread on the
process-global entry cache, so keep this slice owned. Comment updated to
explain the race rather than 'reshaped for borrowck'.

seq: the buf field became write-only after enqueue() now takes &out
directly; delete the field, its Default initializer, and the assignment.
CI's globAllSources() follows the src/cli -> runtime/cli symlink, double-
counting 19 markers (330 vs 311). Dedup via realpath like the sibling
dead-code-escapes lint does.
… PRs merged

#35370, #35392, #35367, #35373 removed 26 markers on main (368->342);
8 of those overlapped with this PR's sites. After rebase this PR removes
342->293 = 49 markers.
@robobun
robobun force-pushed the farm/796fd8f2/borrowck-bucket-B branch from e004aaa to c411f31 Compare July 25, 2026 02:02
@robobun

robobun commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Synced with main (c411f31). #35370, #35392, #35367, #35373 merged while this was in review; 8 of their sites overlapped with this PR. After the rebase this PR removes 49 markers (342 → 293) across 40 files instead of the original 57. Ratchet LIMIT updated to 293.

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

I didn't find bugs in this pass, but this is a 40-file behavior-preserving refactor spanning the bundler, installer, JSC bindings, server, shell, SQL, and paths — including 20 rewritten unsafe launders and one site that already segfaulted CI mid-review — so it's worth a human look before merge. Jarred's question on computeCrossChunkDependencies.rs:339 was answered but the thread is still open.

What was reviewed:

  • IOReader mem::take(&mut s.readers) vs the old .clone() — done/error are terminal, later remove_reader re-entry is a no-op on the emptied list.
  • repl::handle_enter mem::take of line_editor.buffer — every path previously called .clear() afterward, so draining up front is equivalent.
  • Blob.rs switch from t.blob.store() to self.store()t.blob = self.dupe(), same store.
  • patchPackage/install_with_manager dropped .to_vec()lockfile is a separate local, borrowck proves disjointness with &mut manager.
Extended reasoning...

Overview

This PR removes 49 reshaped for borrowck markers across 40 Rust files by replacing porting-era workarounds with idiomatic forms: entry-API instead of get+insert, mem::take+restore instead of .clone(), index+reborrow instead of .collect(), disjoint field borrows instead of raw-pointer launders, and scopeguard::guard(payload, ..) instead of sibling *mut copies. It also deletes the now-dead LazyPackageDestinationDir enum (85 lines) and adds a ratchet test pinning the marker count at 293.

Security risks

None identified. No new user-input parsing, no auth/crypto/TLS paths, no network-facing validation. The changes are internal borrow reshapes; the closest to a security surface is the fd-close scopeguard in jsc_hooks.rs, which is a straightforward payload-carrying guard replacing a raw-pointer capture.

Level of scrutiny

High. Each site is small, but the aggregate touches nearly every subsystem, and roughly half the sites replace unsafe code whose correctness the borrow checker could not previously verify. The PR already produced one aarch64 segfault (filesystem_router.rs, since reverted) during review, which is exactly the failure mode REVIEW.md warns about for "behavior-preserving" refactors. Several sites rely on non-local invariants (e.g. fold.rs get_or_put writing V::default() while p.new_symbol/module_scope_mut() run; server_body leaving state = Err on ?-return being unobservable because the caller panics/aborts). These were argued in-thread but a maintainer sign-off on the reasoning is appropriate.

Other factors

  • Jarred-Sumner left an inline question on the computeCrossChunkDependencies mem::take that was answered by robobun but not resolved.
  • Latest CI (#79970 on c411f31, retriggered as d6ba924) status not yet reported in-thread.
  • Test coverage is via existing suites for each touched area plus the new ratchet lint; the refactor itself has no dedicated behavioral test (which is expected for a no-behavior-change refactor, but shifts confidence onto CI breadth).
  • My two prior inline findings (dead Seq::buf field; filesystem_router segfault) were both addressed.

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.

3 participants