1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Lifetime-extending reborrows for generator call sites.
//!
//! [`BStack::process_gen`](crate::BStack::process_gen) and
//! [`BStack::inplace_gen`](crate::BStack::inplace_gen) take a `FnMut` closure
//! that yields [`BStackGenOp<'a>`](crate::BStackGenOp) values, where `'a` is a
//! lifetime chosen by the *caller* and outlives the whole call. A generator
//! that hands the ops a short-lived scratch buffer — an 8-byte free-list head,
//! say — therefore needs a `&'a mut [u8]` derived from a local the closure
//! captured, and that is exactly what the borrow checker refuses:
//!
//! ```text
//! error[E0521]: borrowed data escapes outside of closure
//! ...captured variable cannot escape `FnMut` closure body
//! ```
//!
//! The rejection is a limitation of the closure model, not a real hazard: a
//! `FnMut` body only ever gets `&mut Env` for the duration of one call, so
//! nothing reborrowed out of a capture can be *proved* to outlive the call,
//! even when the referent is a stack local that plainly does. The same
//! pattern is rejected under Polonius Alpha and is outside the stated scope of
//! Full Polonius, so the workaround is not going away on its own.
//!
//! [`bstack_unsafe_reborrow!`](crate::bstack_unsafe_reborrow) and
//! [`bstack_unsafe_reborrow_mut!`](crate::bstack_unsafe_reborrow_mut) package
//! that workaround: they extend a borrow's lifetime — and *only* its lifetime,
//! the referent type is preserved by construction — so the call site reads as
//! one greppable, visibly-unsafe operation instead of an open-coded
//! [`transmute`](core::mem::transmute). The `unsafe` in each macro's *name*
//! carries the marking, so the call site does not write an `unsafe` block of
//! its own; it still owes the safety obligations below, and should still carry
//! a `// SAFETY:` comment saying why they hold.
//!
//! ```
//! # #[cfg(all(feature = "set", feature = "atomic"))]
//! # fn demo(stack: &bstack::BStack, head_off: u64) -> std::io::Result<()> {
//! use bstack::{BStackGenOp, bstack_unsafe_reborrow, bstack_unsafe_reborrow_mut};
//!
//! let mut head_buf = [0u8; 8];
//! let mut next_buf = [0u8; 8];
//! let mut step = 0u32;
//! stack.process_gen(|| {
//! let op = match step {
//! // Read the free-list head into a scratch buffer.
//! // SAFETY: `head_buf` is declared before this call and never moved,
//! // so it outlives the whole sequence; the op is consumed before the
//! // closure runs again, so no other access to `head_buf` overlaps it.
//! 0 => Some(BStackGenOp::Read {
//! offset: head_off,
//! buf: bstack_unsafe_reborrow_mut!(&mut head_buf[..]),
//! }),
//! // Step 0's read has resolved, so `head_buf` can be inspected here.
//! 1 => {
//! next_buf = u64::from_le_bytes(head_buf).wrapping_add(1).to_le_bytes();
//! // SAFETY: as above; `next_buf` is not touched again after this
//! // step hands it to the write.
//! Some(BStackGenOp::Write {
//! offset: head_off,
//! data: bstack_unsafe_reborrow!(&next_buf[..]),
//! })
//! }
//! _ => None,
//! };
//! step += 1;
//! op
//! })
//! # }
//! # fn main() {}
//! ```
//!
//! # Safety contract
//!
//! Both macros are `unsafe`: they produce a reference whose lifetime the
//! compiler can no longer check, and neither the macro nor the crate can
//! verify any of the following. The caller must guarantee, for every
//! extended reference:
//!
//! 1. **The referent outlives the call.** It must live at least until
//! `process_gen` / `inplace_gen` returns — in practice, a local declared
//! *before* the call and not moved, reallocated, or dropped during it. A
//! buffer created inside the closure body, or a `Vec` that may reallocate
//! mid-sequence, does not qualify.
//! 2. **No overlapping access while the callee holds it.** Between the moment
//! an op is yielded and the moment the callee finishes with it, the referent
//! must not be read or written through any other path, including the
//! closure's own captures. For `process_gen` this is automatic for the
//! common shape: each op is consumed and dropped before the closure is
//! called again, so inspecting a buffer at a later step — after the `Read`
//! that filled it has resolved — is fine.
//! 3. **`inplace_gen` retains write payloads.** Unlike `process_gen`, an
//! `inplace_gen` `Write` is *not* applied immediately: the borrowed `data`
//! is staged and held until the batch commits at the end of the call, and
//! later `Read`s are served from that staging overlay. A buffer handed to a
//! `Write` must therefore be treated as frozen — no mutation, no
//! overlapping mutable reborrow — for the remainder of the call, not merely
//! for that one step.
//!
//! Rule 3 is the one most easily missed, and the only one whose violation is
//! undefined behaviour outright rather than a dangling read: mutating memory
//! behind a live shared reference is UB regardless of what the callee does
//! with it.
//!
//! These macros are a documentation and auditability device, not a proof. New
//! code that can be expressed without them should be.
pub use crate::;
/// Extends a shared borrow's lifetime, preserving the referent type.
///
/// Implementation detail of [`bstack_unsafe_reborrow!`](crate::bstack_unsafe_reborrow);
/// not part of the public API.
///
/// # Safety
///
/// See the [module documentation](self#safety-contract). The referent must
/// outlive `'long` and must not be mutated or mutably reborrowed while the
/// returned reference is live.
pub const unsafe Sized>
/// Extends a mutable borrow's lifetime, preserving the referent type.
///
/// Implementation detail of [`bstack_unsafe_reborrow_mut!`](crate::bstack_unsafe_reborrow_mut);
/// not part of the public API.
///
/// # Safety
///
/// See the [module documentation](self#safety-contract). The referent must
/// outlive `'long` and must not be accessed through any other path while the
/// returned reference is live.
pub const unsafe Sized>
/// Extends a shared borrow so it can escape a `process_gen` / `inplace_gen`
/// generator closure.
///
/// Takes a borrow expression and returns the same reference with a caller-chosen
/// (inferred) lifetime — the referent type is preserved by construction, so this
/// cannot silently reinterpret the pointee the way an open-coded
/// [`transmute`](core::mem::transmute) with an inferred target can. An optional
/// second argument ascribes the resulting reference type for readability:
///
/// ```
/// # use bstack::bstack_unsafe_reborrow;
/// let buf = [0u8; 8];
/// // SAFETY: `buf` outlives every use of `long` below.
/// let long: &[u8] = bstack_unsafe_reborrow!(&buf[..]);
/// // SAFETY: as above.
/// let also: &[u8] = bstack_unsafe_reborrow!(&buf[..], &[u8]);
/// assert_eq!(long, also);
/// ```
///
/// # Safety
///
/// See the [`reborrow` module documentation](crate::reborrow#safety-contract)
/// for the full contract. In short: the referent must outlive the entire
/// enclosing `process_gen` / `inplace_gen` call, and must not be mutated while
/// the extended reference is live — which, for a buffer handed to an
/// `inplace_gen` `Write`, means for the rest of the call, since staged writes
/// are held until the batch commits.
/// Extends a mutable borrow so it can escape a `process_gen` / `inplace_gen`
/// generator closure.
///
/// The mutable counterpart of
/// [`bstack_unsafe_reborrow!`](crate::bstack_unsafe_reborrow); see that macro
/// for the argument forms.
///
/// ```
/// # use bstack::bstack_unsafe_reborrow_mut;
/// let mut buf = [0u8; 8];
/// // SAFETY: `buf` outlives every use of `long`, which is the only live path
/// // to it while it is in scope.
/// let long: &mut [u8] = bstack_unsafe_reborrow_mut!(&mut buf[..]);
/// long[0] = 1;
/// assert_eq!(buf[0], 1);
/// ```
///
/// # Safety
///
/// See the [`reborrow` module documentation](crate::reborrow#safety-contract)
/// for the full contract. In short: the referent must outlive the entire
/// enclosing `process_gen` / `inplace_gen` call, and while the extended
/// reference is live it must be the *only* live path to the referent — no
/// reads, writes, or reborrows through the original binding.