blob: make StoreRef::data_mut unsafe, drop Sync - #30812
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR makes ChangesStoreRef Safety and Atomic Timestamp Fix
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Updated 4:06 PM PT - Aug 15th, 2026
🔄 @robobun, the build for your commit |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/runtime/webcore/blob/copy_file.rs (1)
1376-1407:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winRemove the unnecessary
data_mut()borrows from this setup path.Lines 1385 and 1405 mint
&mut Dataonly to passpathlikeintoprepare_pathlike(), but that helper never mutates thePathOrFileDescriptor. On a sharedStoreRef, this unnecessary mutable borrow reopens the aliasing hazard the PR is trying to eliminate.♻️ Suggested fix
fn prepare_pathlike( - pathlike: &mut PathOrFileDescriptor, + pathlike: &PathOrFileDescriptor, must_close: &mut bool, is_reading: bool, ) -> bun_sys::Result<Fd> { if let PathOrFileDescriptor::Path(path) = pathlike { let fd = match bun_sys::openat_windows_a( self.read_write_loop.destination_fd = match Self::prepare_pathlike( - &mut unsafe { self.destination_file_store.data_mut() } - .as_file_mut() + &self.destination_file_store + .data + .as_file() .pathlike, &mut self.read_write_loop.must_close_destination_fd, false, ) { self.read_write_loop.source_fd = match Self::prepare_pathlike( - &mut unsafe { self.source_file_store.data_mut() } - .as_file_mut() + &self.source_file_store + .data + .as_file() .pathlike, &mut self.read_write_loop.must_close_source_fd, true, ) {🤖 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/webcore/blob/copy_file.rs` around lines 1376 - 1407, The mutable borrows from destination_file_store.data_mut() and source_file_store.data_mut() are unnecessary and reintroduce aliasing risk; change these to immutable borrows (use the non-mutating accessor, e.g., data()/as_file() instead of data_mut()/as_file_mut()) when extracting .pathlike and call Self::prepare_pathlike(...) with that shared reference while preserving the mutable out-parameter read_write_loop.must_close_destination_fd (and the source equivalent); update both assignments to self.read_write_loop.destination_fd and self.read_write_loop.source_fd to use the immutable accessors and keep error handling/throw/mkdirp logic unchanged for CopyFileWindows and prepare_pathlike.
🤖 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/webcore/FileReader.rs`:
- Around line 360-367: The SAFETY comment is insufficient to justify calling
unsafe { store.data_mut() } because data_mut() requires proving no &Data/&mut
Data borrows exist on any thread (worker threads may hold &Data via
StoreRef/Blob clones); fix by removing the unsafe store.data_mut() usage in
on_start and instead clone the File/Blob instance and mutate the cloned File
(take &mut file on the cloned File) so you never call store.data_mut() on the
shared Store; update open_file_blob, ReadFile spawn points, and the on_start
path to operate on the cloned File (or otherwise ensure tasks that spawn
ReadFile run after on_start) to eliminate the aliasing risk and make the SAFETY
reasoning trivial for store.data_mut().
---
Outside diff comments:
In `@src/runtime/webcore/blob/copy_file.rs`:
- Around line 1376-1407: The mutable borrows from
destination_file_store.data_mut() and source_file_store.data_mut() are
unnecessary and reintroduce aliasing risk; change these to immutable borrows
(use the non-mutating accessor, e.g., data()/as_file() instead of
data_mut()/as_file_mut()) when extracting .pathlike and call
Self::prepare_pathlike(...) with that shared reference while preserving the
mutable out-parameter read_write_loop.must_close_destination_fd (and the source
equivalent); update both assignments to self.read_write_loop.destination_fd and
self.read_write_loop.source_fd to use the immutable accessors and keep error
handling/throw/mkdirp logic unchanged for CopyFileWindows and prepare_pathlike.
🪄 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: 53bfa435-5921-4484-a1bd-2bd2e8e97d10
📒 Files selected for processing (7)
src/codegen/bake-codegen.tssrc/jsc/webcore_types.rssrc/runtime/webcore/Blob.rssrc/runtime/webcore/FileReader.rssrc/runtime/webcore/blob/copy_file.rssrc/runtime/webcore/blob/read_file.rstest/js/web/fetch/blob-write.test.ts
CI statusBuild #98737 (sha eee5f40, latest) ran 177 lanes green with zero test failures before being canceled by a maintainer; the evidence check (fail-before/pass-after on ASAN and release) is green. The one red lane on the previous build (#98731, debian-13-x64-asan) was two main-side issues, neither in this diff: the known This PR is the one kept for #30800 (#30805 was closed as contained in it); it is rebased onto current main, MERGEABLE, with all review threads resolved. |
958ecea to
66617cf
Compare
There was a problem hiding this comment.
No new issues found, but this is a substantial Rust soundness audit (unsafe-fn migration across ~22 data_mut/blob_store_mut call sites, !Sync drop, AtomicU64 field conversion, plus an unrelated bake-codegen bootstrap workaround) that's gone through several rounds of correction — worth a human pass before merge. There's also one minor unaddressed inline note on FileReader.rs:382.
Extended reasoning...
Overview
This PR closes #30800 by making StoreRef::data_mut an unsafe fn, removing unsafe impl Sync for StoreRef, converting File.last_modified from JSTimeType (u64) to AtomicU64, and adding a compile-time !Sync trip-wire. It touches 8 files: src/jsc/webcore_types.rs (core type definitions), src/runtime/webcore/{Blob,Body,FileReader}.rs, src/runtime/webcore/blob/{copy_file,read_file}.rs, a new concurrency stress test, and an unrelated src/codegen/bake-codegen.ts bootstrap workaround. Roughly 22 call sites are wrapped in unsafe { } with per-site SAFETY justifications, and several read paths were refactored from data_mut() to shared &store.data borrows.
Security risks
None in the conventional sense — no auth, crypto, network, or input-parsing surface. The change is a Rust memory-model soundness audit. The risk profile is correctness of unsafe-code reasoning: each SAFETY comment is a claim about aliasing/threading that, if wrong, masks UB. Several such claims were already corrected during review (false "sole holder" / "JS thread blocked" justifications, the blob_store_mut sibling back-door, the last_modified data race), and the PR description itself documents a remaining "benign" &mut File vs &File overlap in resolve_file_stat as out-of-scope follow-up.
Level of scrutiny
High. This is production-critical unsafe Rust in the core Blob/Store types that back Bun.file(), fetch bodies, FileReader, and S3. The PR went through four rounds of substantive correction (three from the bug-hunting system, one from CodeRabbit), each of which found real inaccuracies in the SAFETY reasoning. The FileReader::on_start refactor introduces a small behavioral divergence from the Zig original (dropped is_atty write-back to the shared Store) that the author has not yet acknowledged in the comment or addressed. The bundled bake-codegen.ts change is logically unrelated to the soundness fix and should at minimum be called out to the human reviewer.
Other factors
- One unresolved 🟡 inline comment from a prior run (FileReader.rs:382) flags the dropped
is_attycache write-back; impact is negligible but the PR's "no user-visible behavioral delta" claim should account for it. - CI build #54800 has one failing lane (
html-rewriter-leak.test.tson debian-13-x64-asan) that the author argues is a known flake unrelated to this diff; that determination should be confirmed by a maintainer. - The
AtomicU64conversion is type-correct (JSTimeType = u64in Rust per src/jsc/lib.rs:2299) and the newClone for Filesnapshots it viaRelaxedload, which is sound. - Test coverage: a new 16×8 concurrency stress test exercises the worker-thread
ReadFilepath under ASAN; existing blob/streams/globals suites pass per the description.
Given the complexity, the iterative correction history, the documented residual aliasing, and the bundled unrelated codegen change, this warrants human sign-off rather than auto-approval.
There was a problem hiding this comment.
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/jsc/webcore_types.rs`:
- Around line 1238-1253: The unsafe Send impl on StoreRef is relying on a
runtime convention because Data::S3 contains Option<Rc<S3Credentials>> which is
not Send; file a follow-up issue to make this sound by replacing
Rc<S3Credentials> with Arc<S3Credentials>, update all uses (including
S3::init_with_referenced_credentials and any places that clone or drop
S3Credentials) to use Arc cloning semantics, remove the runtime-only caveat from
the unsafe impl once conversion is done, and add tests exercising cross-thread
StoreRef drop paths to ensure no Rc->Arc regressions.
🪄 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: fa7076cb-0b7b-4904-b52a-2a8eb0ab51f3
📒 Files selected for processing (4)
src/jsc/webcore_types.rssrc/runtime/webcore/Blob.rssrc/runtime/webcore/FileReader.rstest/js/web/fetch/blob-write.test.ts
There was a problem hiding this comment.
All prior review feedback has been addressed and I found nothing further on fbf4013, but this is a substantial unsafe-Rust soundness change to core StoreRef/Blob threading semantics (Send/Sync impls, AtomicU64 field conversion, ~22 unsafe call-site justifications across 10 files) that warrants a human sign-off.
Extended reasoning...
Overview
This PR closes #30800 by making StoreRef::data_mut an unsafe fn, removing unsafe impl Sync for StoreRef, converting File::last_modified from JSTimeType to AtomicU64, and adding a compile-time !Sync trip-wire. It touches src/jsc/webcore_types.rs (the canonical Store/StoreRef/File definitions), Blob.rs, Body.rs, FileReader.rs, copy_file.rs, read_file.rs, plus a new concurrency test, two stale-comment fixups in json_lexer.rs/bun-build-api.test.ts, and a bootstrap workaround in bake-codegen.ts.
Security risks
None in the conventional sense (no auth/crypto/input-handling). The risk surface is memory-safety: this PR rewrites the Send/Sync story for an intrusively-refcounted heap type that crosses the JS↔worker-pool thread boundary, converts a field to atomic, and restructures borrow discipline at ~22 data_mut call sites plus 4 blob_store_mut sites. An error here could introduce UB (aliased &mut, torn reads) rather than a logic bug.
Level of scrutiny
High. This is exactly the category the approval guidelines flag: complex, large, touches critical code paths, and centers on unsafe Rust semantics. The PR went through seven rounds of inline review from me (false SAFETY claims, the last_modified worker-thread race, the blob_store_mut sibling hole, the dropped is_atty write-back, the Rc<S3Credentials> Send caveat, stale cross-references), all of which were addressed — but the volume of subtle corrections itself indicates this needs a human maintainer's eyes on the final state, particularly the documented-but-unfixed resolve_file_stat &mut File vs worker-thread &File overlap and the FileReader::on_start behavior change (dropped is_atty cache write-back).
Other factors
- A sibling PR #30805 was flagged as a potential duplicate also closing #30800; a maintainer should reconcile which lands.
- The
bake-codegen.tsJSON.stringifywrapper is an unrelated bootstrap workaround riding along in this PR. - CI on the prior push had one known-flaky failure (
html-rewriter-leak.test.ts) unrelated to this diff. - The bug hunting system found nothing on the current revision (fbf4013).
fbf4013 to
0e8c2f6
Compare
ea7da7d to
9243c97
Compare
There was a problem hiding this comment.
No remaining issues from the automated pass, but this is a substantial Rust soundness change (unsafe-fn boundary moves, !Sync, AtomicU64 field, ~22 SAFETY justifications, an acknowledged &mut File/&File overlap in resolve_file_stat, and an intentional behavioral divergence in FileReader::on_start) that warrants a human sign-off.
Extended reasoning...
Overview
This PR closes #30800 by making StoreRef::data_mut an unsafe fn, removing unsafe impl Sync for StoreRef, converting File::last_modified to AtomicU64, and adding a compile-time !Sync trip-wire. It touches 10 files: the core webcore_types.rs type definitions, ~22 data_mut call sites across Blob.rs/Body.rs/FileReader.rs/read_file.rs/copy_file.rs (each wrapped in unsafe + SAFETY or refactored to shared borrows), the sibling blob_store_mut/set_blob_content_type helpers in Body.rs (also made unsafe fn), a new concurrency stress test, and a small bake-codegen.ts bootstrap workaround plus two stale-comment cleanups.
Security risks
None in the conventional sense — no auth, crypto, network input parsing, or data-exposure surfaces are touched. The risk class here is memory-safety / UB: the PR rearranges &mut-from-&self boundaries on an intrusively-refcounted heap type that crosses threads, and explicitly leaves one acknowledged compiler-level &mut File vs worker-thread &File overlap in resolve_file_stat (documented as idempotent/benign, with full interior-mutability conversion deferred as follow-up). It also documents-but-does-not-fix the pre-existing Rc<S3Credentials> !Send gap under unsafe impl Send for StoreRef.
Level of scrutiny
High. This is a soundness audit of unsafe Rust touching threading, atomics, and aliasing rules in a core webcore type. The PR went through ~8 rounds of review feedback, each surfacing subtle inaccuracies in SAFETY justifications (handle-uniqueness vs borrow-exclusivity conflation, wrong threading-model claims, stale cross-references). All were addressed and resolved, but the iteration count itself demonstrates the subtlety. There is also one intentional behavioral change: FileReader::on_start now clones File and discards open_file_blob's is_atty write-back (diverging from the Zig original), which the PR documents as a deliberate trade-off.
Other factors
- All prior inline comments (mine and CodeRabbit's) are resolved; the bug-hunting system found nothing new on this revision.
- A potential-duplicate bot flagged #30805 as also closing #30800.
- The PR description itself flags maintainer-level decisions: the remaining
resolve_file_stataliasing, theBlob: Syncfollow-up, and theRc→ArcS3 credentials follow-up. - CI history includes one unrelated flake (
html-rewriter-leak.test.ts) on a single lane.
Given the scope, the unsafe-code surface, and the explicit design trade-offs, this should not be auto-approved.
There was a problem hiding this comment.
All my prior feedback has been addressed and I found nothing new this pass — but this is a substantial unsafe-Rust soundness audit (drops Sync, rewrites ~22 data_mut call sites' aliasing contracts, converts File.last_modified to AtomicU64, and leaves a documented-but-unclosed &mut File↔&File overlap in resolve_file_stat), so it warrants a human sign-off.
Extended reasoning...
Overview
This PR closes #30800 by hardening the StoreRef mutable-access contract: data_mut becomes unsafe fn, unsafe impl Sync for StoreRef is removed (with a compile-time trip-wire), File.last_modified becomes AtomicU64 to close a real cross-thread race on the worker-pool ReadFile path, and ~22 call sites across Blob.rs / Body.rs / FileReader.rs / read_file.rs / copy_file.rs are rewritten to either (a) use shared &store.data borrows where mutation isn't needed, or (b) wrap data_mut() in narrowly-scoped unsafe with per-site SAFETY justifications. Two ancillary changes ride along: a JSON.stringify wrapper on OVERLAY_CSS in bake-codegen.ts (bootstrap workaround) and a stale-comment cleanup in json_lexer.rs.
Security risks
None identified. This is an internal Rust soundness/aliasing audit with no user-facing API surface, no auth/crypto/permissions, and no untrusted-input handling changes. The bake-codegen.ts change is a build-time string-quoting tweak.
Level of scrutiny
High. The change is large (~9 files), touches production-critical webcore Blob/Store/file-I/O paths that run on both the JS thread and worker-pool threads, and consists almost entirely of unsafe-block edits whose correctness depends on subtle threading/aliasing invariants. The PR has gone through ~8 rounds of review feedback (mine and CodeRabbit's) correcting SAFETY-comment factual claims — handle-uniqueness vs borrow-exclusivity conflations, wrong thread-model citations, the Rc<S3Credentials> !Send caveat, the blob_store_mut/set_blob_content_type safe back-doors — all now addressed. The remaining resolve_file_stat &mut File vs worker-thread &File overlap is explicitly documented as out-of-scope follow-up rather than closed.
Other factors
- All 12 of my prior inline comments are resolved; the most recent commits (
9243c979,26a4daf9,90ee114c,90d446ac,23982cdd) directly address them. - A sibling PR #30805 also closes #30800 — a maintainer should decide which lands.
- CI history shows one flaky-test failure (
html-rewriter-leak.test.ts) unrelated to this diff. - The PR's own description flags two acknowledged loose ends for follow-up:
Blob: Synctightening andRc<S3Credentials>→Arc. - No CODEOWNERS hit that I'm aware of, but the scope (core webcore unsafe Rust + cross-thread invariants) is well outside what I'd approve without a maintainer look.
23982cd to
f378b91
Compare
b8db52c to
6c329fe
Compare
There was a problem hiding this comment.
All prior inline feedback has been addressed and I found nothing new on this revision, but this is a substantial soundness audit of StoreRef/Store (unsafe-fn boundary changes across ~22 call sites, AtomicU64 conversion, a documented remaining data race in resolve_file_stat, and an intentional behavioral divergence in FileReader::on_start) — worth a maintainer's eyes before merge.
Extended reasoning...
Overview
This PR closes #30800 by making StoreRef::data_mut an unsafe fn, removing unsafe impl Sync for StoreRef, converting File::last_modified to AtomicU64, and adding a compile-time !Sync trip-wire. It touches six source files in src/jsc/ and src/runtime/webcore/ (Blob, Body, FileReader, read_file, copy_file, webcore_types) plus two tests. The change is a Rust-API soundness audit with no intended user-visible behavioral delta — every data_mut() call site (~22) is wrapped in unsafe {} with a SAFETY comment, several read-only sites are converted to shared &store.data borrows, blob_store_mut/set_blob_content_type/shared_view_raw/set_is_ascii_flag are also made unsafe fn, and FileReader::on_start is refactored to clone File locally instead of mutating the shared Store.
Security risks
No direct security-sensitive surface (auth/crypto/permissions). However, this is memory-safety code in a core intrusively-refcounted data structure that crosses thread boundaries via the work pool. The PR explicitly documents a remaining data-level race (resolve_file_stat's JS-thread &mut File vs. WriteFile::run_with_fd's worker-thread &File reading non-atomic seekable/mode) as benign-in-practice and out of scope; and the unsafe impl Send for StoreRef still relies on a runtime convention (S3 stores never cross threads) rather than a type-system guarantee. Both are pre-existing, honestly documented, and flagged for follow-up — but they are the kind of thing a maintainer should sign off on rather than a bot.
Level of scrutiny
High. This is not a mechanical change: it redraws the unsafe boundary for a shared-mutable data structure used by Blob/File/Response/Request/S3/streams, and the correctness of each SAFETY comment depends on non-local threading and re-entrancy invariants. The PR went through ~18 rounds of inline review feedback (all now resolved) correcting SAFETY-comment accuracy, stale cross-references, and one structural fix (the AtomicU64 conversion after I flagged a real cross-thread race). That iteration history itself indicates the subtlety involved.
Other factors
- All prior inline threads resolved. The latest commit (a46029c) addressed my most recent comment about
shared_view_raw/set_is_ascii_flagbeing safe back-doors; no new issues found on re-review. - Intentional behavioral divergence.
FileReader::on_startnow discardsopen_file_blob'sis_attywrite-back to the sharedStore(documented as a deliberate trade-off vs. reintroducing the aliasing hazard). Impact is negligible but it is a divergence from the Zig original. - Duplicate PR. github-actions flagged #30805 as also closing #30800; a maintainer should confirm which one lands.
- Test coverage. The new concurrency test exercises the primary cross-thread path under ASAN; the source-level
storeref-not-sync.test.tsand compile-time trip-wire guard the invariants. The PR description is candid that a runtime fail→pass demonstration isn't possible for a soundness-only change.
Given the scope, the documented-but-unclosed races, and the potential duplicate, this warrants human review rather than auto-approval.
…t unsafe fn Comment-cop flagged the paragraph-length justifications; cut them to the load-bearing SAFETY contracts and cross-references (the canonical borrow discipline lives on resolve_size). resolve_file_stat was the remaining safe fn discharging data_mut's precondition with a caller-context claim, so it now carries the contract in its signature like its four siblings; its three callers discharge it at the call site. Also drop a stray reject-path qualifier and fix the dead-code-escapes guard path.
…dress review nits The previous commit's fail-before simulation restored webcore_types.rs from HEAD before committing, silently reverting its comment trims; this re-applies them. Also: drop the nonexistent init_with_referenced_credentials citation (the real Rc-sharing site is Rc::clone(s3.get_credentials()) in Blob.rs), make get_last_modified's dead non-File arm unreachable! like its five siblings, and move the source-level guard to test/internal/source-lints/ where pure source-text lints belong.
Closes #30800.
Bug
StoreRef::data_mut(&self) -> &mut Dataminted a&mutfrom a shared&selfvia a raw-pointer deref:With
Sync, two threads can share a&StoreRefand each calldata_mut()— two
&mut Datato the same backing allocation, immediate UB through asafe API. Even single-threaded, two back-to-back
data_mut()calls on thesame
&StoreRefalias two&mut Data.Fix
Four complementary changes:
data_mutisunsafe fnwith a precondition of exclusivity (noother
&/&mutto the pointee live for the borrow's duration, onthis thread or any other). Every call site (~22) gets a SAFETY note
describing its single-owner context.
unsafe impl Sync for StoreRefremoved.Sendis retained: theintrusive
ThreadSafeRefCountmakes moving the handle between threadssound, and the threadpool tasks that carry a
StoreRefrequire onlySend, notSync.File.last_modifiedis nowAtomicU64(closes the race flaggedin review).
do_read_fileclones the backingStoreRefinto eachReadFiletask it spawns, so N concurrentfile.bytes()callsschedule N workers that all observe the same
Storeallocation.Previously the workers all wrote
file.last_modifiedon that sharedStoreconcurrently — technically UB even though every writer storesthe same
fstat-derived mtime so the race is idempotent. TheAtomicU64+ shared-borrow (&store.data) path on worker threadscloses the observable data race; the JS-thread read in
get_last_modifieduses the same atomic load (Relaxed).__store_ref_not_syncmodule uses theauto-trait-ambiguity pattern (same trick as
src/runtime/shell/subproc.rs__pipe_reader_thread_confined): ifStoreRefever regainsSync, two blanketNotSyncCheckimplsapply and
_NOT_SYNCfails to compile with "conflicting impls".Remaining narrow aliasing (documented)
resolve_file_stat(called from JS-threadget_last_modified/resolve_size/resolved_sizeonly on the first access before aworker has run) still materializes
&mut Fileto writemax_size/mode/seekable. Two worker-pool&Fileborrowers canoverlap it:
ReadFiletasks, which only touch theAtomicU64last_modified(the atomic closes that observable race), and POSIXWriteFile::run_with_fd, which reads the non-atomicseekable/modeon fd-backed destinations — a pre-existing data-level race this PR
documents but does not close. Both overlaps are benign in practice:
every overlapping writer produces the same stat-derived value
(idempotent) and
seekable/mode/max_sizeare smallCopytypeswritten once per
Storelifetime. Converting the rest ofFiletointerior-mutable fields would close both and is legitimate follow-up
work but out of scope for this soundness-audit PR.
Verification
cargo check --workspace— clean, no new warnings.bun bd test test/js/web/fetch/blob*.test.ts test/js/web/streams/readable-stream-blob-consumed.test.ts test/js/bun/globals.test.js— 44 blob + 20 globals + 1 streams tests pass under ASAN.
Bun.file().bytes() is safe under high concurrencyfans out16 files × 8 concurrent reads, each cloning the backing
StoreRefinto an independent
ReadFiletask — the primaryworker-thread-reaches-into-shared-
Storepath — and asserts everytask returns the full uncorrupted file bytes under
bun bd(ASAN).Regression guards
Like the sibling soundness PRs (#30773 etc.), this is a Rust-API-soundness
change with no user-visible behavioral delta — the existing call sites
produced identical output before and after (every writer stores the same
idempotent mtime, whether through a plain assignment or a
Relaxedatomicstore), so a runtime fail→pass demonstration around the JS-visible API is
not possible. The regression guards are:
__store_ref_not_synctrip-wire inwebcore_types.rs(any future
unsafe impl Sync for StoreReffails the build withconflicting impls);
unsafe fn data_mutsignature (forces new call sites to justifytheir exclusivity claim in writing);
test/internal/storeref-not-sync.test.ts, a source-level guard in thestyle of
test/internal/source-lints/dead-code-escapes.test.tsthat asserts thethree invariants on the text of
webcore_types.rs(nounsafe impl Sync for StoreRef,data_mutspelledpub unsafe fn,trip-wire present). All three assertions fail against the pre-fix
source and pass with this PR, giving a readable suite-level failure
alongside the trip-wire's rustc error.
Separately,
bun bdbootstrap in the gate container trips on apre-existing bake-codegen failure related to #30679 (the bootstrap
release-bun is older than the JSON-lexer fix that lets
define:acceptraw CSS). A tiny
JSON.stringifywrapper on theOVERLAY_CSSvalue isincluded as a standalone commit to unblock
bun bdin stale-bootstrapenvironments; it's semantically identical to the current form once the
lexer fix propagates but works with both old and new release-bun
snapshots.
Rebase on main (conflict resolution)
The review iterations were collapsed into a single commit so the diff can
be rebased cleanly onto
main's evolving Blob refactors; the net changeis the six webcore source files plus the two regression tests.
The one non-trivial conflict was with #33599 ("encode content_type
ownership in the type"), which refactored the exact mechanism this PR's
Body.rschanges touched: it replaced the manualcontent_typerawpointer +
content_type_allocatedflag with aBlobContentTypetype anddropped the
allocatedparameter fromset_blob_content_type. Tookmain's superseding mechanism wholesale (
blob.content_type.set(BlobContentType::from(mime_type)),set_blob_content_type(blob, mime_type)) and re-applied only this PR'ssoundness layer on top:
blob_store_mutandset_blob_content_typeareunsafe fn, and their call sites (the body-consumer content-type andMimeType::TEXTsites) writeunsafe { … }+ SAFETY. No behavioralchange.
Earlier rebases took main's deletion of
src/parsers/json_lexer.rs(JSON-parser rewrite #33032) as-is (this PR's only change there was a
comment fix) and dropped an unrelated
bake-codegen.tsbootstrap tweak,and reconciled with the
Delete PathStringrefactor (#31579) in thestructured-clone deserialization site.
Verified after the final rebase:
bun bd testgreen on the two guards(14 pass) plus
body.test.ts+blob.test.ts(376 pass, including#33599's "does not free a static pointer" ownership tests), and the
source-level guard still fails 0/3 against main's
webcore_types.rs.[review] gate passed · iteration 44 · 8 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 2 passed · 0 rejected · iteration 44
evidence per changed file
root cause · written by the author bot
The root cause was that StoreRef declared unsafe impl Sync while its safe method data_mut(&self) minted a &mut Data from a shared reference via a raw pointer, so two threads sharing a &StoreRef could obtain aliasing mutable references, which is undefined behavior, and File::last_modified writes could also race. The fix removes the Sync impl, makes data_mut an unsafe fn with a documented exclusivity contract, and converts call sites to shared borrows or tightly scoped unsafe blocks so mutable access only occurs where exclusivity actually holds. It also changes File::last_modified to an Atomi…