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}