Summary
Found in src/http_jsc/websocket_client.rs:1756 during the unsafe audit. A raw-pointer workaround bypasses the borrow checker because it cannot prove two references point to disjoint allocations.
// src/http_jsc/websocket_client.rs:1744-1756
let vm_ptr: *mut _ = vm;
// SAFETY: rare_data() returns &mut RareData from a SEPARATE Box allocation,
// so it does not overlap with &*vm_ptr (a shared &VirtualMachine).
unsafe { (*vm_ptr).rare_data().ws_client_group::<SSL>(&*vm_ptr) }
Why borrowck rejects this
rare_data(&mut self) borrows the entire VirtualMachine mutably from borrowck's perspective, and &*vm_ptr wants a shared borrow of the same struct — conflict.
Why it's actually sound (under Stacked Borrows / Tree Borrows)
rare_data returns self.rare_data.as_mut().unwrap() where rare_data: Option<Box<RareData>>. The returned &mut RareData points into the Box's separate heap allocation, not into VirtualMachine itself. Under Stacked Borrows' per-allocation model, &VirtualMachine and &mut RareData have different tags on different allocations, so no aliasing violation.
The fragility
The soundness depends on:
rare_data() always returning a reference to a separate allocation (currently a Box)
ws_client_group() never accessing vm.rare_data
These invariants are only documented in comments — the compiler doesn't enforce them. If rare_data() is refactored to use an inline field instead of a Box, this pattern silently becomes UB.
Suggestions
-
Document the invariant on rare_data():
/// SAFETY NOTE: callers rely on this returning a reference to a
/// SEPARATE heap allocation (Box), not to a field of VirtualMachine
/// itself. This enables raw-pointer borrow-splitting patterns.
pub fn rare_data(&mut self) -> &mut RareData { ... }
-
Consider #[field_projection] attributes (future Rust feature): annotate methods that only borrow specific fields, letting borrowck do field-level analysis.
-
Consider Miri CI check: run affected files under Miri to catch violations if the invariant ever breaks.
-
Audit similar patterns: this is the most prominent instance, but the triage found 27+ needs_dive entries — several likely share this pattern.
Related
Summary
Found in
src/http_jsc/websocket_client.rs:1756during the unsafe audit. A raw-pointer workaround bypasses the borrow checker because it cannot prove two references point to disjoint allocations.Why borrowck rejects this
rare_data(&mut self)borrows the entireVirtualMachinemutably from borrowck's perspective, and&*vm_ptrwants a shared borrow of the same struct — conflict.Why it's actually sound (under Stacked Borrows / Tree Borrows)
rare_datareturnsself.rare_data.as_mut().unwrap()whererare_data: Option<Box<RareData>>. The returned&mut RareDatapoints into the Box's separate heap allocation, not intoVirtualMachineitself. Under Stacked Borrows' per-allocation model,&VirtualMachineand&mut RareDatahave different tags on different allocations, so no aliasing violation.The fragility
The soundness depends on:
rare_data()always returning a reference to a separate allocation (currently a Box)ws_client_group()never accessingvm.rare_dataThese invariants are only documented in comments — the compiler doesn't enforce them. If
rare_data()is refactored to use an inline field instead of a Box, this pattern silently becomes UB.Suggestions
Document the invariant on
rare_data():Consider
#[field_projection]attributes (future Rust feature): annotate methods that only borrow specific fields, letting borrowck do field-level analysis.Consider Miri CI check: run affected files under Miri to catch violations if the invariant ever breaks.
Audit similar patterns: this is the most prominent instance, but the triage found 27+
needs_diveentries — several likely share this pattern.Related