Skip to content

unsafe: raw-pointer borrowck workaround — should add compiler-checkable invariants #30767

Description

@discord9

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:

  1. rare_data() always returning a reference to a separate allocation (currently a Box)
  2. 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

  1. 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 { ... }
  2. Consider #[field_projection] attributes (future Rust feature): annotate methods that only borrow specific fields, letting borrowck do field-level analysis.

  3. Consider Miri CI check: run affected files under Miri to catch violations if the invariant ever breaks.

  4. Audit similar patterns: this is the most prominent instance, but the triage found 27+ needs_dive entries — several likely share this pattern.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions