Conversation
…ference Every callback behind a Batch::from site recovers the embedding object from the task pointer with container-of and uses its other fields, and HTTPThread::schedule does so on the calling thread. The task pointer therefore has to be projected out of a pointer to the object; projecting it out of a reference (`&raw mut self.task`, `addr_of_mut!(x.task)`, `&raw mut v.last_mut().unwrap().task`) retags it to the field's range under Stacked Borrows, and the last_mut() loops also invalidated the pointers already in the batch on every iteration. PatchTask::schedule takes the task pointer its only caller holds; AsyncHTTP::schedule projects through ptr::from_mut(self) and preconnect goes through it; the isolated-install start_task and the source-map batch project through ptr::from_mut of the slot; ThreadPool::each and the bundler's CSS and part-range batches fill their Vec first and then project through as_mut_ptr().add(i). Add a source lint banning the reference-based spellings in Batch::from arguments.
|
Warning Review limit reached
Next review available in: 25 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (8)
Comment |
|
Updated 3:05 PM PT - Aug 12th, 2026
❌ @robobun, your commit 36c69aa has 5 failures in
🧪 To try this PR locally: bunx bun-pr 37894That installs a local version of the PR into your bun-37894 --bun |
|
Status: fix and lint are up (head 36c69aa); waiting on CI. How the defect was confirmed: each of the eleven Review so far: the comment-length findings are addressed (each site is down to its one-line SAFETY note; the |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
Not a duplicate of #37865, though the two are siblings and were checked against each other. #37865 converts one site, The disagreement the bot noticed is in the prose: #37865's lint header says |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. The pointer-provenance conversions look correct and behavior-preserving at every site, and the new source lint's classifier matches what it claims. Because it touches intrusive-task scheduling across five subsystems (thread pool, bundler, HTTP client, package manager, isolated install) with subtle Stacked Borrows reasoning, changes PatchTask::schedule from a safe method to an unsafe fn(*mut Self), and coordinates with sibling PRs #37768 / #37865, a human look would still be worthwhile.
What was reviewed:
- Each of the eleven converted sites: verified the new projection (
ptr::from_mut,as_mut_ptr().add(i), or raw*mut Self) yields the same address with whole-object provenance, and that the batch/schedule call sequence is unchanged. generateChunksInParallel.rs: confirmed splitting fill-then-batch preserves the exact task set and order for both the CSS-AST and part-range loops.preconnect→schedulerefactor:Batch::default()+pushproduces the identical single-task batch as the old directBatch::from.flush_patch_task_queue:(*patch_task).callback.is_apply()matches the oldmatches!(pt.callback, Apply(_)); no&mut PatchTaskis formed.
Extended reasoning...
Overview
This PR converts eleven Batch::from(..) call sites that hand an intrusive *mut Task to a thread pool so that the task-field pointer is projected out of a raw pointer to the embedding object rather than out of a reference. The affected sites span src/threading/ThreadPool.rs (each_impl), src/bundler/LinkerContext.rs (compute_data_for_source_map), src/bundler/linker_context/generateChunksInParallel.rs (two batches), src/http/AsyncHTTP.rs (schedule and preconnect), src/install/isolated_install/Installer.rs (start_task), src/install/patch_install.rs (schedule, now unsafe fn(this: *mut Self, ..)), and its sole caller in src/install/PackageManager/runTasks.rs. A new source-lint test bans the reference-based projection shape going forward.
The rationale is that every callback behind these sites recovers the whole embedding object via container_of / from_field_ptr! and touches sibling fields; bun_core::container_of documents that the input must be derived from the object's pointer, and under Stacked Borrows a &raw mut <ref>.field retag covers only the field. The PR description includes a Miri reduction demonstrating each shape. Tree Borrows (what bun run rust:miri runs) accepts the old shapes, and no runtime crash is expected — the addresses are identical; only provenance changes.
Security risks
None. This is a pointer-provenance / aliasing-model correctness change with no user-facing input handling, no auth/crypto/permissions surface, and no data-flow changes. The pointer values produced are byte-identical to before.
Level of scrutiny
High. Every touched site is on a concurrency-critical path: intrusive thread-pool task hand-off in the bundler's parallel chunk generation and source-map computation, the HTTP client's request scheduling (including fetch.preconnect), the package manager's patch-task and isolated-install task queues, and ThreadPool::each itself. The changes are mechanically simple per site (rebase the field projection onto a raw pointer), but the correctness argument depends on Stacked Borrows semantics that are not enforced by the compiler or by bun run rust:miri (Tree Borrows). I traced each site's before/after to confirm the projected address is unchanged and the batch contents/order are preserved; for the generateChunksInParallel.rs loops I confirmed the fill-then-batch split yields the identical task set, and for the iter_mut()-based sites (LinkerContext, Installer) I confirmed ptr::from_mut(element) gives whole-element provenance that survives iterator advancement (matching the PR's from_mut Miri mode passing).
Other factors
- API change:
PatchTask::schedulegoes from a safe&mut selfmethod to anunsafe fn(this: *mut Self, ..)with a documented Safety contract. Its only caller (flush_patch_task_queue) already holds the*mut PatchTaskfrom the fifo and now avoids forming the intermediate&mut PatchTask. This is the right shape for the contract but is a signature change a maintainer should sign off on. - Sibling-PR coordination: The PR is deliberately disjoint from #37865 (which handles the accessor /
&mutargument shapes) and #37768 (WorkPool::schedule). The duplicate-PR bot flagged #37865 and the author explained the split; #37865's lint header states&raw mut self.taskhas whole-object range, which this PR contradicts (correctly, under Stacked Borrows). That coordination is worth a maintainer's eye. - CI: Build #93422 is still running with an x64-musl build failure in
scripts/build/ci.tsand an unrelated Windows-SDK header clang warning. I could not determine whether the musl failure is related to this change. - Test coverage: The new lint has self-tests for its classifier and matcher covering every converted spelling and the accepted shapes; the ratcheted allowlist is empty. The PR description lists the existing test suites exercised for each converted path (patch install, isolated install, audit, fetch, bundler HTML/CSS/splitting/sourcemaps), all passing on the debug build. The comment-cop bot's length findings were addressed in follow-up commits and all threads are resolved.
- No prior
claude[bot]reviews on this PR.
|
On the one open point in the review above: the x64-musl build failure in build 93422 is the dependency fetch step (c-ares, mimalloc, the WebKit tarball and lol-html all failed to download from github.com after 5 attempts), not anything this branch compiles. The same outage shows up in that build's two red tests that download from github.com (sharp's libvips in complex-workspace.test.ts on the aarch64 lanes, protoc in test-tonic.test.ts); those and the worker_threads exception-check assertion on the ASAN lane are reported separately. Every lane that ran the install, fetch and bundler suites exercised by this change passed, as did the source-lints job that runs the new lint. |
Problem
Batch(patch install, isolated install, async HTTP,ThreadPool::each, the bundler's source-map and chunk batches) take the task pointer out of a&mutreference to the object that embeds it.last_mut()loops are worse: eachlast_mut()invalidates the pointers already in the batch, soBatch::pushitself trips.bun run rust:mirichecks, accepts these shapes. Same contract violation as Hand intrusive work-pool tasks to the pool through the object's pointer, not a reference #37768 and install: schedule the Windows hardlink task through the allocation's own pointer #37865, in the one spelling neither covers.Fix
*mutthe caller already holds,ptr::from_mutof the slot just written, oras_mut_ptr().add(i)in a second loop that runs after the Vec is full.PatchTask::scheduletakes*mut Self, since its one caller already holds the raw pointer, and HTTPpreconnectnow goes throughAsyncHTTP::scheduleso the http crate has one projection site.Batch::fromargument, reports exactly the eleven sites on main and nothing here, and is disjoint from install: schedule the Windows hardlink task through the allocation's own pointer #37865's lint. Existing install, fetch, bundler and source-map tests pass on this branch's debug build;fetch.preconnectwas checked by hand.Background
Tasknode (callback plusnextlink), aBatchis a linked list threaded through those nodes, and the pool calls each callback with the node's own pointer. The pool allocates nothing.*mut Taskto get the embedding object back.bun_core::container_ofdocuments that this needs a pointer derived from the object's pointer; a reborrow of just the field does not qualify.&raw mut obj.fieldtaken through a reference down to that field;&raw mut (*p).fieldthrough a raw pointer keepsp's range. Tree Borrows is looser and is what bun's Miri run uses, so it misses this class.test/internal/source-lints/) are bun tests that scan the Rust tree for a banned spelling, with a per-file allowlist that only ratchets down. They are how a pattern Miri does not cover is kept out of the tree.Original description
Problem
The remaining
Batch::from(..)sites that hand an intrusive task to a thread pool projected the*mut Taskout of a reference to the object instead of out of the object's pointer:PatchTask::schedule(&mut self, ..)(src/install/patch_install.rs)Batch::from(&raw mut self.task)AsyncHTTP::schedule(&mut self, ..)andpreconnect(src/http/AsyncHTTP.rs)Batch::from(addr_of_mut!(self.task)),addr_of_mut!(async_http.task)withasync_http: &mut AsyncHTTPInstaller::start_task(src/install/isolated_install/Installer.rs)Batch::from(&raw mut task.task)withtask = &mut self.tasks[i]ThreadPool::each/each_ptr(src/threading/ThreadPool.rs)Batch::from(addr_of_mut!(runner_task.task))fromtasks.iter_mut()compute_data_for_source_map(src/bundler/LinkerContext.rs)Batch::from(&raw mut line_offset.thread_task)fromiter_mut(), twicegenerate_chunks_in_parallel(src/bundler/linker_context/generateChunksInParallel.rs)push(..); Batch::from(&raw mut tasks.last_mut().unwrap().task)per element, four loopsIn every case the pool calls back with exactly that pointer and the callback recovers the object from it with container-of and then uses the rest of the object:
PatchTask::from_task_ptr(run_from_thread_pool),from_field_ptr!(Task, ..)(isolated install, which also writesresultand linksnext),from_field_ptr!(RunnerTask, ..)(each, readsiandctx),from_field_ptr!(SourceMapDataTask, ..),from_field_ptr!(PrepareCssAstTask, ..)andpending_part_range_prologue. ForAsyncHTTPit happens before the task is even queued:HTTPThread::schedulecontainer-ofs every task in the batch on the calling thread, links the object intoqueued_tasksthrough the result, andstart_queued_tasklaterptr::reads the whole struct through it.bun_core::container_ofdocuments that this requires a pointer derived from the object's pointer ("a&mut fieldreborrow does not suffice"), andWorkPool::schedule_ownedprojects out of theBox::into_rawpointer for that reason. The otherBatch::fromsites in the tree already do this, either inline (&raw mut (*parse_task).task,addr_of_mut!((*task).task)) or by passing a pointer a helper projected that way (&raw mut (*task).threadpool_taskin the package manager's enqueue helpers);WorkPool::scheduleforwards its argument, and the one site that narrows through an accessor is #37865's.The reference-based spelling does not meet that contract. rustc retags the result of
&raw mut <place>unless the place is based on a raw pointer, and under Stacked Borrows that retag covers the projected field only, so the callback's first sibling-field access is out of range. Thepush+last_mut()loops ingenerate_chunks_in_parallelhave a second problem: everylast_mut()goes throughVec::deref_mut, which reborrows the whole buffer and invalidates the pointers already in the batch, soBatch::pushitself trips when it links the next task onto the previous one (projecting throughptr::from_mut(last_mut())is not enough there; the batch has to be built after the Vec is filled). Reduction below. Tree Borrows, which is whatbun run rust:mirichecks, accepts all of these shapes, none of the crates involved are in its crate set, and no crash is known or expected from today's codegen: the addresses are right, and this only matters to the aliasing model. It is the same contract violation as #37768 (theWorkPool::schedulepopulation, which deliberately leavesBatch::fromout of its lint) and #37865 (NewTaskQueue::push, the oneBatch::fromsite that narrows through an accessor; not touched here), in the one spelling neither of them covers.Reduction under Miri (miri 0.1.0 9f36de775b), one mode per shape
refis the&raw mut task.task/&raw mut self.taskshape,iter_muttheaddr_of_mut!(runner_task.task)shape,last_mutthe chunk loops;last_mut_from_mutis the chunk loop with only the projection fixed;from_mut,as_mut_ptrandraware the three shapes this PR converts to. The pool side iscontainer_offollowed by a sibling-field read and write, run on another thread. The trailing comments on the-->lines name the statement at each location.Fix
Same pointer values as before at every site, so no behaviour change; what changes is which pointer they are projected from.
PatchTask::schedulebecomesunsafe fn schedule(this: *mut Self, batch)and projects out ofthis. Its only caller,flush_patch_task_queue, already holds the*mut PatchTaskit popped from the fifo and now reads the callback kind through it instead of forming a&mut PatchTask.AsyncHTTP::schedulekeeps&mut self(its callers,send_sync,NetworkTask,FetchTasklet, S3, hold the object by reference or value, andHTTPThread::schedulerecovers anAsyncHTTP, which a&mut AsyncHTTPdoes cover) and projects throughptr::from_mut(self), a reborrow of the whole object.preconnectgoes through it instead of projecting by hand, so the http crate has one projection site.Installer::start_taskandcompute_data_for_source_mapproject throughptr::from_mutof the slot they just wrote.ThreadPool::each_impland the two batches ingenerate_chunks_in_parallelfill theirVecfirst and then build the batch in one loop fromas_mut_ptr().add(i); the three copies of thelast_mut()push in the chunk loop collapse into that one loop, and the with-capacity comments that justified taking pointers mid-fill go away with the pattern.Verification
test/internal/source-lints/thread-pool-batch-projection.test.tsbans&raw mut/&raw const/addr_of_mut!/addr_of!of a field path rooted at a binding as the argument of anyBatch::from(call (however the type is spelled, including thePoolBatch/ThreadPoolBatchaliases), with a self-test over the spellings of every site in the tree and the converted shapes. Against main it reports exactly the eleven sites above:and passes on this branch with no allowlist. It is deliberately disjoint from #37865's lint, which bans the reference / accessor /
from_mutshapes in the same argument and reports onlysrc/install/PackageInstall.rs:475both on main and on this branch (checked by running it here), so the two can land in either order without sharing an allowlist; #37768's lint coversWorkPool::schedule, and both of those also report nothing on this branch.The converted paths are exercised by existing tests, all run against the debug build of this branch:
test/cli/install/bun-install-patch.test.ts(18 pass) andbun-patch.test.ts(31 pass) forPatchTask::scheduleand the registry downloads that go throughNetworkTask→AsyncHTTP::schedule;test/cli/install/isolated-install.test.ts(62 pass) forstart_task;test/cli/install/bun-audit.test.ts(17 pass) for thesend_syncpath;test/js/web/fetch/fetch-redirect.test.ts(30 pass) andclient-fetch.test.ts(34 pass) for the fetch path;test/bundler/bundler_html.test.ts(22 pass; JS, CSS and HTML part ranges plus the CSS AST batch),test/bundler/css/css-modules.test.ts(6 pass),test/bundler/bundler_splitting.test.ts(11 pass; many chunks througheach_ptr),test/bundler/bun-build-api.test.ts(52 pass; source maps) andtest/js/bun/sourcemap/internal-sourcemap-roundtrip.test.ts(22 pass).fetch.preconnectwas checked directly with a local listener (the preconnect opens the connection before the firstfetch);test/js/web/fetch/fetch-preconnect.test.tsitself cannot run in this container becauselocalhostresolves to::1first here, which makes it fail identically on the released binary, and that is tracked separately.cargo clippy --no-depsonbun_threading,bun_http,bun_installandbun_bundleris clean andcargo fmtis clean.