Skip to main content

hopper_runtime/
segment_lease.rs

1//! RAII-leased typed segment guards.
2//!
3//! The Hopper Safety Audit called out that `SegmentBorrowRegistry` was
4//! being used as an **instruction-sticky ledger**: every `segment_ref` /
5//! `segment_mut` call appended an entry, nothing ever released, and the
6//! entries outlived the returned `Ref<T>` / `RefMut<T>` for the rest of
7//! the instruction. That model makes legitimate sequential patterns
8//! like
9//!
10//! ```ignore
11//! { let mut b = ctx.segment_mut::<WireU64>(0, BAL)?; *b += amount; }
12//! { let mut b = ctx.segment_mut::<WireU64>(0, BAL)?; *b += more;   }
13//! ```
14//!
15//! impossible inside one instruction, because the second call would
16//! collide with the lingering entry from the first.
17//!
18//! [`SegmentLease`], [`SegRef`], and [`SegRefMut`] replace that model
19//! with real RAII: the registry entry lives exactly as long as the
20//! returned typed guard, and dropping the guard releases the entry.
21//! Sequential non-overlapping (and sequential same-region-read-then-
22//! write) patterns now behave exactly the way Rust borrowers expect.
23//!
24//! ## Representation
25//!
26//! `SegmentLease` stores a raw pointer to the registry plus a
27//! `PhantomData<&'a mut SegmentBorrowRegistry>`. Raw is necessary
28//! because the returned `SegRef<T>` otherwise exclusively borrows the
29//! whole `Context`, which would prevent even reading *another* account
30//!, a regression far worse than the sticky behavior we are fixing.
31//! The `PhantomData` ties the lease's lifetime to the registry's, so
32//! use-after-free is impossible at the type level. Drop performs a
33//! single swap-remove; no allocation, no heap touch.
34//!
35//! ## Why a wrapper, not a field on `Ref`/`RefMut`
36//!
37//! The canonical `hopper_runtime::Ref` / `RefMut` are kept flat on
38//! Solana (`{ptr, state_ptr}` = 2 words, see `borrow.rs`). Adding a
39//! registry pointer to them would re-inflate the flat representation
40//! for every access path, even the whole-account `load()` path that
41//! doesn't touch the segment registry. Keeping the lease as a separate
42//! wrapper means `load()` stays at 2 words and only segment access
43//! pays for the lease (one extra pointer-word on Solana).
44
45use core::marker::PhantomData;
46use core::ops::{Deref, DerefMut};
47
48use crate::borrow::{Ref, RefMut};
49use crate::segment_borrow::{SegmentBorrow, SegmentBorrowRegistry};
50
51// ══════════════════════════════════════════════════════════════════════
52//  SegmentLease
53// ══════════════════════════════════════════════════════════════════════
54
55/// RAII lease on one registered entry in a
56/// [`SegmentBorrowRegistry`](crate::segment_borrow::SegmentBorrowRegistry).
57///
58/// On drop, the lease removes the registered entry via swap-remove.
59/// It is returned wrapped inside [`SegRef`] / [`SegRefMut`]; callers
60/// should not construct a `SegmentLease` directly.
61///
62/// # Safety invariants
63///
64/// The raw pointer is valid for `'a` because the lease was created
65/// from a `&'a mut SegmentBorrowRegistry`. No other code writes to the
66/// registry while a lease exists *from the caller's perspective*,
67/// because the enclosing `SegRef<T>` / `SegRefMut<T>` owns the lease.
68/// Drop runs exactly once.
69pub struct SegmentLease<'a> {
70    registry: *mut SegmentBorrowRegistry,
71    borrow: SegmentBorrow,
72    _lt: PhantomData<&'a mut SegmentBorrowRegistry>,
73}
74
75impl<'a> SegmentLease<'a> {
76    /// Construct a lease from a live `&mut SegmentBorrowRegistry` and
77    /// the borrow that was just registered.
78    ///
79    /// # Safety
80    ///
81    /// The caller must ensure `borrow` was registered in `registry`
82    /// immediately before this call, and no path other than dropping
83    /// the returned lease will remove the entry.
84    ///
85    /// `pub` but `#[doc(hidden)]` so cross-crate Hopper code
86    /// (`hopper-core`'s `Frame`, macro-generated accessors) can build
87    /// leases without rebuilding the primitive; end users of Hopper
88    /// should reach for `AccountView::segment_ref` / `segment_mut`
89    /// instead, which wrap this constructor safely.
90    #[doc(hidden)]
91    #[inline(always)]
92    pub unsafe fn new(
93        registry: &'a mut SegmentBorrowRegistry,
94        borrow: SegmentBorrow,
95    ) -> Self {
96        Self {
97            registry: registry as *mut _,
98            borrow,
99            _lt: PhantomData,
100        }
101    }
102
103    /// The borrow entry this lease owns, for diagnostics.
104    #[inline(always)]
105    pub fn borrow(&self) -> &SegmentBorrow {
106        &self.borrow
107    }
108}
109
110impl<'a> Drop for SegmentLease<'a> {
111    #[inline(always)]
112    fn drop(&mut self) {
113        // SAFETY: `_lt` pins `'a` to the registry's borrow; the pointer
114        // is valid for the full lifetime of `self`. `release` is a
115        // bounded-array swap-remove, no allocation, no panic path.
116        unsafe {
117            (*self.registry).release(&self.borrow);
118        }
119    }
120}
121
122impl<'a> core::fmt::Debug for SegmentLease<'a> {
123    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124        f.debug_struct("SegmentLease")
125            .field("borrow", &self.borrow)
126            .finish_non_exhaustive()
127    }
128}
129
130// ══════════════════════════════════════════════════════════════════════
131//  SegRef / SegRefMut
132// ══════════════════════════════════════════════════════════════════════
133
134/// Shared typed segment guard: a [`Ref<T>`](crate::borrow::Ref) paired
135/// with a [`SegmentLease`] that releases the registry entry on drop.
136///
137/// `SegRef<T>` derefs to `T`, so call sites written against the
138/// previous `Ref<T>`-returning signatures compile unchanged in the
139/// vast majority of cases (pattern bindings that explicitly named
140/// `Ref<'_, T>` need the one-word substitution to `SegRef<'_, T>`).
141pub struct SegRef<'a, T: ?Sized> {
142    inner: Ref<'a, T>,
143    lease: SegmentLease<'a>,
144}
145
146impl<'a, T: ?Sized> SegRef<'a, T> {
147    /// Assemble a `SegRef` from a pre-built inner guard and lease.
148    ///
149    /// Doc-hidden public constructor for cross-crate use (Frame,
150    /// generated accessors). Prefer `AccountView::segment_ref` /
151    /// `Context::segment_ref` / `Frame::segment_ref` in user code.
152    #[doc(hidden)]
153    #[inline(always)]
154    pub fn new(inner: Ref<'a, T>, lease: SegmentLease<'a>) -> Self {
155        Self { inner, lease }
156    }
157
158    /// Consume the guard and return the underlying pointer.
159    ///
160    /// The lease and account-level borrow are still released on drop
161    /// of the returned components; this escape hatch is provided for
162    /// rare generic plumbing.
163    #[inline(always)]
164    pub fn into_parts(self) -> (Ref<'a, T>, SegmentLease<'a>) {
165        (self.inner, self.lease)
166    }
167
168    /// Raw `*const T` of the borrowed data.
169    #[inline(always)]
170    pub fn as_ptr(&self) -> *const T {
171        self.inner.as_ptr()
172    }
173
174    /// Access the underlying `Ref<T>` without dropping the lease.
175    #[inline(always)]
176    pub fn inner(&self) -> &Ref<'a, T> {
177        &self.inner
178    }
179}
180
181impl<T: ?Sized> Deref for SegRef<'_, T> {
182    type Target = T;
183    #[inline(always)]
184    fn deref(&self) -> &T {
185        &*self.inner
186    }
187}
188
189impl<T: ?Sized> core::fmt::Debug for SegRef<'_, T> {
190    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
191        f.debug_struct("SegRef")
192            .field("lease", &self.lease)
193            .finish_non_exhaustive()
194    }
195}
196
197/// Exclusive typed segment guard.
198///
199/// Mirror of [`SegRef`] for the mutable path. Derefs mutably to `T`.
200pub struct SegRefMut<'a, T: ?Sized> {
201    inner: RefMut<'a, T>,
202    lease: SegmentLease<'a>,
203}
204
205impl<'a, T: ?Sized> SegRefMut<'a, T> {
206    /// Assemble a `SegRefMut` from a pre-built inner guard and lease.
207    ///
208    /// Doc-hidden public constructor, see [`SegRef::new`].
209    #[doc(hidden)]
210    #[inline(always)]
211    pub fn new(inner: RefMut<'a, T>, lease: SegmentLease<'a>) -> Self {
212        Self { inner, lease }
213    }
214
215    /// Consume the guard and return its parts.
216    #[inline(always)]
217    pub fn into_parts(self) -> (RefMut<'a, T>, SegmentLease<'a>) {
218        (self.inner, self.lease)
219    }
220
221    #[inline(always)]
222    pub fn as_ptr(&self) -> *const T {
223        self.inner.as_ptr()
224    }
225
226    #[inline(always)]
227    pub fn as_mut_ptr(&mut self) -> *mut T {
228        self.inner.as_mut_ptr()
229    }
230}
231
232impl<T: ?Sized> Deref for SegRefMut<'_, T> {
233    type Target = T;
234    #[inline(always)]
235    fn deref(&self) -> &T {
236        &*self.inner
237    }
238}
239
240impl<T: ?Sized> DerefMut for SegRefMut<'_, T> {
241    #[inline(always)]
242    fn deref_mut(&mut self) -> &mut T {
243        &mut *self.inner
244    }
245}
246
247impl<T: ?Sized> core::fmt::Debug for SegRefMut<'_, T> {
248    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249        f.debug_struct("SegRefMut")
250            .field("lease", &self.lease)
251            .finish_non_exhaustive()
252    }
253}