hopper_native/borrow.rs
1//! Deterministic borrow guards for account data.
2//!
3//! `Ref` and `RefMut` provide RAII borrow tracking on the `borrow_state`
4//! field of `RuntimeAccount`. When dropped, they restore the borrow
5//! state, preventing use-after-free and double-mutable-borrow bugs.
6//!
7//! These replace `core::cell::RefCell` without requiring alloc.
8
9use crate::NOT_BORROWED;
10
11/// Shared (immutable) borrow guard for account data.
12///
13/// On drop, decrements the borrow count in `RuntimeAccount.borrow_state`.
14pub struct Ref<'a, T: ?Sized> {
15 value: &'a T,
16 state: *mut u8,
17}
18
19impl<'a, T: ?Sized> Ref<'a, T> {
20 /// Narrow a guard to a field or slice without releasing its account borrow.
21 #[inline]
22 pub fn map<U: ?Sized>(orig: Self, f: impl FnOnce(&T) -> &U) -> Ref<'a, U> {
23 let value = f(orig.value);
24 let (_, state) = orig.into_raw_parts();
25 Ref { value, state }
26 }
27
28 /// Narrow a guard, returning the original guard and error on failure.
29 #[inline]
30 pub fn try_map<U: ?Sized, E>(
31 orig: Self,
32 f: impl FnOnce(&T) -> Result<&U, E>,
33 ) -> Result<Ref<'a, U>, (Self, E)> {
34 match f(orig.value) {
35 Ok(value) => {
36 let (_, state) = orig.into_raw_parts();
37 Ok(Ref { value, state })
38 }
39 Err(error) => Err((orig, error)),
40 }
41 }
42
43 /// Narrow a guard, returning the original guard if the field is absent.
44 #[inline]
45 pub fn filter_map<U: ?Sized>(
46 orig: Self,
47 f: impl FnOnce(&T) -> Option<&U>,
48 ) -> Result<Ref<'a, U>, Self> {
49 Self::try_map(orig, |value| f(value).ok_or(())).map_err(|(orig, ())| orig)
50 }
51
52 /// Create a new shared borrow guard.
53 ///
54 /// The caller must have already incremented `*state` to reflect
55 /// the new shared borrow.
56 #[inline(always)]
57 pub(crate) fn new(value: &'a T, state: *mut u8) -> Self {
58 Self { value, state }
59 }
60
61 /// Create a shared guard whose aliasing is enforced outside the native
62 /// account borrow byte.
63 ///
64 /// Runtime segment access uses this after `SegmentBorrowRegistry` has
65 /// leased the exact byte range. Drop must therefore avoid changing the
66 /// whole-account `borrow_state` byte.
67 #[inline(always)]
68 pub(crate) fn new_external(value: &'a T) -> Self {
69 Self {
70 value,
71 state: core::ptr::null_mut(),
72 }
73 }
74
75 /// Create a shared borrow guard from raw parts.
76 ///
77 /// # Safety
78 ///
79 /// The caller must ensure:
80 /// - The borrow state at `state` was already incremented
81 /// - `value` is valid for lifetime `'a`
82 /// - `state` points to a valid `RuntimeAccount.borrow_state`
83 #[inline(always)]
84 pub unsafe fn from_raw_parts(value: &'a T, state: *mut u8) -> Self {
85 Self { value, state }
86 }
87
88 /// Decompose into raw parts without running the destructor.
89 ///
90 /// The caller takes responsibility for eventually releasing the
91 /// borrow (decrementing `*state`).
92 #[inline(always)]
93 pub fn into_raw_parts(self) -> (&'a T, *mut u8) {
94 let value = self.value;
95 let state = self.state;
96 core::mem::forget(self);
97 (value, state)
98 }
99}
100
101impl<T: ?Sized> core::ops::Deref for Ref<'_, T> {
102 type Target = T;
103
104 #[inline(always)]
105 fn deref(&self) -> &T {
106 self.value
107 }
108}
109
110impl<T: ?Sized> Drop for Ref<'_, T> {
111 fn drop(&mut self) {
112 if self.state.is_null() {
113 return;
114 }
115 // SAFETY: state points to RuntimeAccount.borrow_state in the
116 // BPF input buffer. We decrement the shared borrow count,
117 // restoring NOT_BORROWED when the last shared borrow is released.
118 unsafe {
119 let current = *self.state;
120 if current == 1 {
121 *self.state = NOT_BORROWED;
122 } else {
123 *self.state = current - 1;
124 }
125 }
126 }
127}
128
129/// Exclusive (mutable) borrow guard for account data.
130///
131/// On drop, restores `RuntimeAccount.borrow_state` to `NOT_BORROWED`.
132pub struct RefMut<'a, T: ?Sized> {
133 value: &'a mut T,
134 state: *mut u8,
135}
136
137impl<'a, T: ?Sized> RefMut<'a, T> {
138 /// Narrow an exclusive guard without releasing its account borrow.
139 #[inline]
140 pub fn map<U: ?Sized>(orig: Self, f: impl FnOnce(&mut T) -> &mut U) -> RefMut<'a, U> {
141 match Self::try_map(orig, |value| Ok::<_, core::convert::Infallible>(f(value))) {
142 Ok(mapped) => mapped,
143 Err((_, never)) => match never {},
144 }
145 }
146
147 /// Narrow a guard, preserving the original guard on an error. A closure
148 /// may itself mutate data before returning an error; those edits are retained.
149 #[inline]
150 pub fn try_map<U: ?Sized, E>(
151 orig: Self,
152 f: impl FnOnce(&mut T) -> Result<&mut U, E>,
153 ) -> Result<RefMut<'a, U>, (Self, E)> {
154 // Move the original exclusive reference before deriving the projection.
155 // Moving it afterwards would retag the parent and invalidate the child
156 // pointer under Stacked Borrows. Keep unwind release separate from that
157 // reference so a panicking closure does not strand the account lease.
158 struct ReleaseOnUnwind(*mut u8);
159 impl Drop for ReleaseOnUnwind {
160 fn drop(&mut self) {
161 if !self.0.is_null() {
162 // SAFETY: this guard owns the original exclusive lease;
163 // its account header outlives the mapping call.
164 unsafe { *self.0 = crate::NOT_BORROWED };
165 }
166 }
167 }
168 let mut orig = core::mem::ManuallyDrop::new(orig);
169 let state = orig.state;
170 let unwind = ReleaseOnUnwind(state);
171 match f(&mut **orig) {
172 Ok(value) => {
173 let ptr = value as *mut U;
174 core::mem::forget(unwind);
175 // SAFETY: the closure's reference is derived from the original
176 // guard or is independently valid for that borrow. The original
177 // guard is consumed without releasing its exclusive lease; the
178 // new guard owns that same lease for the original lifetime.
179 Ok(RefMut {
180 // SAFETY: `ptr` retains the exclusive lease described above.
181 value: unsafe { &mut *ptr },
182 state,
183 })
184 }
185 Err(error) => {
186 core::mem::forget(unwind);
187 Err((core::mem::ManuallyDrop::into_inner(orig), error))
188 }
189 }
190 }
191
192 /// Narrow a guard, returning the original guard if the field is absent.
193 #[inline]
194 pub fn filter_map<U: ?Sized>(
195 orig: Self,
196 f: impl FnOnce(&mut T) -> Option<&mut U>,
197 ) -> Result<RefMut<'a, U>, Self> {
198 Self::try_map(orig, |value| f(value).ok_or(())).map_err(|(orig, ())| orig)
199 }
200
201 /// Create a new exclusive borrow guard.
202 ///
203 /// The caller must have already set `*state = 0` to indicate
204 /// exclusive borrow.
205 #[inline(always)]
206 pub(crate) fn new(value: &'a mut T, state: *mut u8) -> Self {
207 Self { value, state }
208 }
209
210 /// Create an exclusive guard whose aliasing is enforced by an external
211 /// segment lease rather than the whole-account borrow byte.
212 #[inline(always)]
213 pub(crate) fn new_external(value: &'a mut T) -> Self {
214 Self {
215 value,
216 state: core::ptr::null_mut(),
217 }
218 }
219
220 /// Create an exclusive borrow guard from raw parts.
221 ///
222 /// # Safety
223 ///
224 /// The caller must ensure:
225 /// - The borrow state at `state` was set to 0 (exclusive)
226 /// - `value` is valid and unique for lifetime `'a`
227 /// - `state` points to a valid `RuntimeAccount.borrow_state`
228 #[inline(always)]
229 pub unsafe fn from_raw_parts(value: &'a mut T, state: *mut u8) -> Self {
230 Self { value, state }
231 }
232
233 /// Decompose into raw parts without running the destructor.
234 ///
235 /// The caller takes responsibility for eventually releasing the
236 /// borrow (restoring `*state` to `NOT_BORROWED`).
237 #[inline(always)]
238 pub fn into_raw_parts(self) -> (&'a mut T, *mut u8) {
239 let manual = core::mem::ManuallyDrop::new(self);
240 // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
241 let value = unsafe { core::ptr::read(&manual.value) };
242 let state = manual.state;
243 (value, state)
244 }
245}
246
247impl<T: ?Sized> core::ops::Deref for RefMut<'_, T> {
248 type Target = T;
249
250 #[inline(always)]
251 fn deref(&self) -> &T {
252 self.value
253 }
254}
255
256impl<T: ?Sized> core::ops::DerefMut for RefMut<'_, T> {
257 #[inline(always)]
258 fn deref_mut(&mut self) -> &mut T {
259 self.value
260 }
261}
262
263impl<T: ?Sized> Drop for RefMut<'_, T> {
264 fn drop(&mut self) {
265 if self.state.is_null() {
266 return;
267 }
268 // SAFETY: state points to RuntimeAccount.borrow_state.
269 // Restore to NOT_BORROWED when the exclusive borrow is released.
270 unsafe {
271 *self.state = NOT_BORROWED;
272 }
273 }
274}