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}