Conversation
…own pointer NewTaskQueue::push handed the thread pool a *mut WorkPoolTask obtained from HasWorkPoolTask::task(), a &mut reborrow of the task field, so the pointer the pool passed back to run_from_thread_pool only covered that field while the callback container-ofs it back to the HardLinkWindowsInstallTask, writes err through it and frees the Box through it. Project the field out of the into_raw pointer itself (IntrusiveField::field_of) instead, the shape WorkPool::schedule_owned and UninstallTask in the same file already use, and recover the object with IntrusiveWorkTask::from_task_ptr so both directions share one offset declaration. HasWorkPoolTask only existed for this site and is replaced by intrusive_work_task!. Adds a source lint over the argument of every Batch::from call that bans projecting the task out of a reference to the field.
|
Warning Review limit reached
Next review available in: 15 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 (2)
Comment |
|
Updated 2:05 PM PT - Aug 12th, 2026
❌ @robobun, your commit ea61fa1 has some failures in 🧪 To try this PR locally: bunx bun-pr 37865That installs a local version of the PR into your bun-37865 --bun |
|
Status: fix and lint are in place; CI is running. Reproduced how: the new Windows x64 debug build of this branch: a 51-file nested local tarball installs with all 51 files hard-linked to the cache, a |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. The change looks correct and follows the established IntrusiveWorkTask::field_of / from_task_ptr pattern, but since it touches unsafe pointer-provenance handling in a thread-pool hand-off path, a human look would still be worthwhile.
What was reviewed:
field_ofprojects viabyte_addon the rawinto_rawpointer, so the scheduled*mut Taskcarries whole-allocation provenance for the callback's container-of andheap::take.intrusive_work_task!andfrom_task_ptrshare oneoffset_of!declaration, so outbound projection and inbound recovery cannot drift;HasWorkPoolTaskhad no other users.- The new source-lint follows the sibling lints in
test/internal/source-lints/(non-vacuous scan guard, self-tested classifier, ratcheted allowlist).
Extended reasoning...
Overview
This PR fixes a pointer-provenance issue in NewTaskQueue::push (src/install/PackageInstall.rs, Windows-only bun install hardlink backend). The old code derived the *mut WorkPoolTask handed to the thread pool from a &mut self.task reborrow via a local HasWorkPoolTask::task() accessor, which under Stacked/Tree Borrows carries provenance for the field only — but the callback run_from_thread_pool recovers the whole HardLinkWindowsInstallTask via container-of, reads/writes sibling fields, and frees the allocation with heap::take. The fix bounds push on IntrusiveWorkTask and schedules Batch::from(TaskType::field_of(task)), projecting the field directly out of the heap::into_raw pointer (byte_add on the raw pointer, no intermediate reference). HardLinkWindowsInstallTask gets intrusive_work_task!(.., task) and the callback uses Self::from_task_ptr, so both directions share one OFFSET constant. The now-unused HasWorkPoolTask trait is deleted. A new source lint (thread-pool-batch-field-ref.test.ts) scans every Batch::from( argument tree-wide and bans the reference-derived shapes.
Security risks
None identified. This is an aliasing-model correctness fix; the address handed to the pool is unchanged, so runtime behaviour on today's codegen is identical. No untrusted input, auth, crypto, or network surface is touched.
Level of scrutiny
High. This is unsafe Rust in a threading hand-off — precisely the "native code: memory safety" category REVIEW.md calls the most-blocked. The change itself is small and mechanical (converge on the canonical IntrusiveField::field_of / from_task_ptr pattern already used by WorkPool::schedule_owned and every other Batch::from site in the tree), and it's the third in a series of identical-shape fixes (#37768, #37772). I verified: field_of in bun_core is this.byte_add(OFFSET).cast() on the raw pointer (provenance-preserving); intrusive_work_task! expands to intrusive_field! which sets OFFSET = offset_of!(Self, task); from_task_ptr wraps from_field_ptr with the same offset; Batch::from still receives *mut WorkPoolTask as before; and grep confirms HasWorkPoolTask had no other users. The source lint mirrors the structure of the existing lints (self-receiver-reclaim.test.ts, frozen-nonnull-reborrow.test.ts) with a non-vacuous scan guard and classifier self-test.
Other factors
The PR description is thorough, cites the bun_core::container_of contract, cross-references the two sibling PRs, and reports cargo check passing on both Windows targets plus the Linux host. The lint's allowlist is empty and ratcheted. Everything I checked lines up. That said, unsafe pointer-provenance changes in a thread-pool ownership hand-off are exactly where a human maintainer with the relevant memory-model context should sign off rather than an automated review, so I'm deferring rather than approving.
|
Cross-reference from #37894, which converts the remaining One prose point for whenever this is next touched: the header here says |
Problem
bun installhardlink backend hands each link task to the thread pool as a pointer taken from a&mutaccessor to the task's embedded pool node, so the pointer covers that one field only.bun run rust:mirichecks) rejects the free.Fix
bun install --backend hardlinkof a 51-file tarball linked every file, a--forcereinstall succeeded, and the install tests that use this queue pass.cargo checkpasses for both Windows targets. Make the Windows hardlink install backend link serially #33113 would delete this path; if it lands first, this change goes with it.Background
&mut obj.fieldcovers the field; a raw projection from*mut Obj(&raw mut (*p).field,addr_of_mut!) covers the object. Same address either way, so this only shows up under Miri.intrusive_work_task!declares which field of a struct is its pool node and derives both projections (object to node, node to object) from that one fact.test/internal/source-lints/holdsbun testfiles that scan the Rust tree for a banned shape and fail on any occurrence outside a per-file allowlist that only ratchets down.Original description
Problem
NewTaskQueue::push(src/install/PackageInstall.rs, Windows only, thebun installhardlink backend) handed the thread pool its task like this:task()is&mut self.task, so the*mut WorkPoolTaskthe pool gets is derived from a reborrow of thetaskfield and carries provenance for that field only, even thoughtask: *mut HardLinkWindowsInstallTask, the pointerheap::into_rawreturned ininit(), was right there. The pool callsrun_from_thread_poolwith exactly that pointer, and the callback treats it as a pointer to the whole object:from_field_ptr!walks back to theHardLinkWindowsInstallTask,run()readssrc_len/basenameand writes throughbytes, the error path writeserr, and both paths end inheap::take, which frees the Box through it. All of that is outside the range the pointer was derived from.bun_core::container_of's contract says so explicitly ("a&mut fieldreborrow does not suffice"); Stacked Borrows rejects the first out-of-range access and Tree Borrows (the modelbun run rust:mirichecks) rejects the free.No crash is known. The address is right, so today's codegen does what was intended; this is an aliasing-model violation of the same shape as the
FileCloseraccessor in #37768 and the zlibtask().as_ptr()site in #37772, in the one population (Batch::from, the rawThreadPoolAPI) neither of those touches. The otherBatch::fromsites in the tree, includingUninstallTaska few hundred lines down in the same file, already project the field out of the object's pointer (addr_of_mut!((*task).task),&raw mut (*p).task); this was the only one that went through an accessor.Fix
pushboundsTaskType: IntrusiveWorkTaskand schedulesBatch::from(TaskType::field_of(task)), projecting the field out of theinto_rawpointer itself, the same thingWorkPool::schedule_owned(src/threading/work_pool.rs) does.HardLinkWindowsInstallTaskgetsbun_threading::intrusive_work_task!(HardLinkWindowsInstallTask, task)andrun_from_thread_poolrecovers the object withSelf::from_task_ptr, so the outbound projection and the inbound container-of share one offset declaration.HasWorkPoolTaskexisted only for this site and is removed.The pointer value the pool receives is unchanged (same field of the same allocation), so there is no behaviour change; the task's provenance now matches what the callback does with it, which is what makes
heap::takein the callback correct rather than correct by accident.#33113 would delete this code path altogether by linking serially; it has been open since June with conflicts and a pending design question, so this fixes the site as it stands. If that lands first, this change goes away with it.
Verification
test/internal/source-lints/thread-pool-batch-field-ref.test.tsscans the argument of everyBatch::from(call (however the type is reached, including thePoolBatch/ThreadPoolBatchaliases) and bans projecting the task out of a reference to the field: a method chain rooted at a receiver ((*task).task(),this.task(),self.task.as_ptr()), a reference formed in the argument (&mut task.task), orfrom_mut/from_ref. Raw projections (&raw mut (*p).task,addr_of_mut!(..)), associated fns given the object's pointer (T::field_of(p)) and locals pass; the self-test covers the spellings of all 30 sites in the tree. Against main it reports exactlyand passes with this branch, with no allowlist.
WorkPool::schedule, the other hand-over, is the subject of #37772's lint; this one is scoped toBatch::fromso the two do not share an allowlist.Behaviour, on a Windows x64 debug build of this branch:
bun install --backend hardlink --linker hoistedof a local tarball with 51 files across nested directories installs all 51 as hard links of the cache entry (the nested directories take themkdir_recursive_os_pathbranch ofrun()), a--forcereinstall goes through the queue's re-init path and succeeds, andtest/cli/install/bun-add.test.ts(54 pass) andbun-link.test.ts(4 pass) pass; every package those install on Windows goes throughNewTaskQueue::push.cargo check -p bun_installpasses forx86_64-pc-windows-msvc,aarch64-pc-windows-msvcand the Linux host;cargo clippy -p bun_installon the host is clean, and on the Windows target reports the same pre-existing items as main (none on the changed lines);cargo fmtis clean.bun test test/internal/source-lints/passes (87 tests).