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(registry: &'a mut SegmentBorrowRegistry, borrow: SegmentBorrow) -> Self {
93        Self {
94            registry: registry as *mut _,
95            borrow,
96            _lt: PhantomData,
97        }
98    }
99
100    /// The borrow entry this lease owns, for diagnostics.
101    #[inline(always)]
102    pub fn borrow(&self) -> &SegmentBorrow {
103        &self.borrow
104    }
105}
106
107impl<'a> Drop for SegmentLease<'a> {
108    #[inline(always)]
109    fn drop(&mut self) {
110        // SAFETY: `_lt` pins `'a` to the registry's borrow; the pointer
111        // is valid for the full lifetime of `self`. `release` is a
112        // bounded-array swap-remove, no allocation, no panic path.
113        unsafe {
114            (*self.registry).release(&self.borrow);
115        }
116    }
117}
118
119impl<'a> core::fmt::Debug for SegmentLease<'a> {
120    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121        f.debug_struct("SegmentLease")
122            .field("borrow", &self.borrow)
123            .finish_non_exhaustive()
124    }
125}
126
127// ══════════════════════════════════════════════════════════════════════
128//  SegRef / SegRefMut
129// ══════════════════════════════════════════════════════════════════════
130
131/// Shared typed segment guard: a [`Ref<T>`](crate::borrow::Ref) paired
132/// with a [`SegmentLease`] that releases the registry entry on drop.
133///
134/// `SegRef<T>` derefs to `T`, so call sites written against the
135/// previous `Ref<T>`-returning signatures compile unchanged in the
136/// vast majority of cases (pattern bindings that explicitly named
137/// `Ref<'_, T>` need the one-word substitution to `SegRef<'_, T>`).
138pub struct SegRef<'a, T: ?Sized> {
139    inner: Ref<'a, T>,
140    lease: SegmentLease<'a>,
141}
142
143impl<'a, T: ?Sized> SegRef<'a, T> {
144    /// Assemble a `SegRef` from a pre-built inner guard and lease.
145    ///
146    /// Doc-hidden public constructor for cross-crate use (Frame,
147    /// generated accessors). Prefer `AccountView::segment_ref` /
148    /// `Context::segment_ref` / `Frame::segment_ref` in user code.
149    #[doc(hidden)]
150    #[inline(always)]
151    pub fn new(inner: Ref<'a, T>, lease: SegmentLease<'a>) -> Self {
152        Self { inner, lease }
153    }
154
155    /// Consume the guard and return the underlying pointer.
156    ///
157    /// The lease and account-level borrow are still released on drop
158    /// of the returned components; this escape hatch is provided for
159    /// rare generic plumbing.
160    #[inline(always)]
161    pub fn into_parts(self) -> (Ref<'a, T>, SegmentLease<'a>) {
162        (self.inner, self.lease)
163    }
164
165    /// Raw `*const T` of the borrowed data.
166    #[inline(always)]
167    pub fn as_ptr(&self) -> *const T {
168        self.inner.as_ptr()
169    }
170
171    /// Access the underlying `Ref<T>` without dropping the lease.
172    #[inline(always)]
173    pub fn inner(&self) -> &Ref<'a, T> {
174        &self.inner
175    }
176}
177
178impl<T: ?Sized> Deref for SegRef<'_, T> {
179    type Target = T;
180    #[inline(always)]
181    fn deref(&self) -> &T {
182        &*self.inner
183    }
184}
185
186impl<T: ?Sized> core::fmt::Debug for SegRef<'_, T> {
187    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
188        f.debug_struct("SegRef")
189            .field("lease", &self.lease)
190            .finish_non_exhaustive()
191    }
192}
193
194/// Exclusive typed segment guard.
195///
196/// Mirror of [`SegRef`] for the mutable path. Derefs mutably to `T`.
197pub struct SegRefMut<'a, T: ?Sized> {
198    inner: RefMut<'a, T>,
199    lease: SegmentLease<'a>,
200}
201
202impl<'a, T: ?Sized> SegRefMut<'a, T> {
203    /// Assemble a `SegRefMut` from a pre-built inner guard and lease.
204    ///
205    /// Doc-hidden public constructor, see [`SegRef::new`].
206    #[doc(hidden)]
207    #[inline(always)]
208    pub fn new(inner: RefMut<'a, T>, lease: SegmentLease<'a>) -> Self {
209        Self { inner, lease }
210    }
211
212    /// Consume the guard and return its parts.
213    #[inline(always)]
214    pub fn into_parts(self) -> (RefMut<'a, T>, SegmentLease<'a>) {
215        (self.inner, self.lease)
216    }
217
218    #[inline(always)]
219    pub fn as_ptr(&self) -> *const T {
220        self.inner.as_ptr()
221    }
222
223    #[inline(always)]
224    pub fn as_mut_ptr(&mut self) -> *mut T {
225        self.inner.as_mut_ptr()
226    }
227}
228
229impl<T: ?Sized> Deref for SegRefMut<'_, T> {
230    type Target = T;
231    #[inline(always)]
232    fn deref(&self) -> &T {
233        &*self.inner
234    }
235}
236
237impl<T: ?Sized> DerefMut for SegRefMut<'_, T> {
238    #[inline(always)]
239    fn deref_mut(&mut self) -> &mut T {
240        &mut *self.inner
241    }
242}
243
244impl<T: ?Sized> core::fmt::Debug for SegRefMut<'_, T> {
245    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
246        f.debug_struct("SegRefMut")
247            .field("lease", &self.lease)
248            .finish_non_exhaustive()
249    }
250}