Skip to main content

cel_runtime/
raw_stack.rs

1use crate::memory::align_index;
2use crate::raw_vec::RawVec;
3use crate::value_type::ValueType;
4use std::mem::MaybeUninit;
5use std::mem::size_of;
6
7/// A simple raw stack that stores values as raw bytes. Each value is naturally aligned given the
8/// base alignment of the stack, which is the maximum alignment of any value stored in the stack.
9/// Live values are owned until popped, explicitly released, or destroyed in reverse push order.
10#[derive(Debug)]
11pub struct RawStack {
12    buffer: RawVec,
13    owners: Vec<fn(&mut RawStack)>,
14    raw_owners: Vec<RawOwner>,
15}
16
17/// Records layout and optional erased destruction only for a raw-byte slot.
18#[derive(Debug)]
19struct RawOwner {
20    depth: usize,
21    before: usize,
22    align: usize,
23    size: usize,
24    ty: Option<ValueType>,
25}
26
27/// Restores a stack checkpoint unless explicitly disarmed.
28struct UnwindGuard<'a> {
29    stack: &'a mut RawStack,
30    byte_origin: usize,
31    depth: usize,
32    armed: bool,
33}
34
35impl Drop for UnwindGuard<'_> {
36    /// Resumes suffix cleanup, including while another destructor unwinds.
37    fn drop(&mut self) {
38        if self.armed {
39            self.stack.unwind_to(self.byte_origin, self.depth);
40        }
41    }
42}
43
44impl Drop for RawStack {
45    /// Destroys every live owner in reverse order.
46    ///
47    /// - Complexity: O(n) plus destructor costs for n live slots.
48    fn drop(&mut self) {
49        self.unwind_to(0, 0);
50    }
51}
52
53impl RawStack {
54    /// Creates a new `RawStack` with base alignment.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use cel_runtime::raw_stack::RawStack;
60    /// let stack = RawStack::with_base_alignment(align_of::<u32>());
61    /// ```
62    #[must_use]
63    pub fn with_base_alignment(base_alignment: usize) -> Self {
64        RawStack {
65            buffer: RawVec::with_base_alignment(base_alignment),
66            owners: Vec::new(),
67            raw_owners: Vec::new(),
68        }
69    }
70
71    /// Returns the logical slot count, including zero-sized values.
72    pub(crate) fn depth(&self) -> usize {
73        self.owners.len()
74    }
75
76    /// Destroys the top owner after removing its ownership and preceding padding.
77    ///
78    /// - Precondition: The stack has at least one logical slot.
79    /// - Postcondition: The preceding prefix retains its bytes and ownership,
80    ///   including when the top value is zero-sized or its destructor panics.
81    /// - Complexity: O(p) for p padding bytes, plus destructor costs.
82    pub(crate) fn drop_top(&mut self) {
83        debug_assert!(self.depth() > 0);
84        let destroy = *self.owners.last().expect("top owner exists");
85        destroy(self);
86    }
87
88    /// Removes the latest ownership entries without destroying or changing their bytes.
89    ///
90    /// - Precondition: `count` does not exceed the logical depth.
91    /// - Complexity: O(count), excluding descriptor destruction.
92    pub(crate) fn release_top(&mut self, count: usize) {
93        debug_assert!(count <= self.depth());
94        let depth = self.depth() - count;
95        while self
96            .raw_owners
97            .last()
98            .is_some_and(|owner| owner.depth > depth)
99        {
100            self.raw_owners.pop();
101        }
102        self.owners.truncate(depth);
103    }
104
105    /// Installs erased destruction for the latest raw-byte slot.
106    ///
107    /// - Precondition: The top slot is raw, has no erased owner yet, and contains a
108    ///   valid value with the shape and layout described by `ty`.
109    pub(crate) fn own_top(&mut self, ty: ValueType) {
110        let depth = self.depth();
111        let owner = self.raw_owners.last_mut().expect("top slot must be raw");
112        debug_assert_eq!(owner.depth, depth);
113        debug_assert!(owner.ty.is_none());
114        debug_assert_eq!(owner.size, ty.size());
115        debug_assert_eq!(owner.align, ty.align());
116        owner.ty = Some(ty);
117    }
118
119    /// Destroys the suffix after `depth` and restores its preceding byte origin.
120    ///
121    /// Remaining destruction resumes if one destructor panics.
122    ///
123    /// - Precondition: `(byte_origin, depth)` identifies a live prefix checkpoint.
124    /// - Complexity: O(n) plus destructor costs for n removed slots and padding scans.
125    pub(crate) fn unwind_to(&mut self, byte_origin: usize, depth: usize) {
126        debug_assert!(depth <= self.depth());
127        debug_assert!(byte_origin <= self.len());
128        let mut guard = UnwindGuard {
129            stack: self,
130            byte_origin,
131            depth,
132            armed: true,
133        };
134        while guard.stack.depth() > depth {
135            guard.stack.drop_top();
136        }
137        guard.stack.buffer.truncate(byte_origin);
138        guard.armed = false;
139    }
140
141    /// Runs a callback, restoring a live prefix checkpoint on error or panic.
142    ///
143    /// - Precondition: `(byte_origin, depth)` identifies a live prefix that the
144    ///   callback preserves.
145    /// - Postcondition: Success retains all callback changes.
146    /// - Complexity: Callback costs, plus suffix destruction on failure.
147    ///
148    /// # Errors
149    ///
150    /// Returns the callback's error after destroying its suffix.
151    pub(crate) fn with_unwind<R>(
152        &mut self,
153        byte_origin: usize,
154        depth: usize,
155        callback: impl FnOnce(&mut RawStack) -> anyhow::Result<R>,
156    ) -> anyhow::Result<R> {
157        debug_assert!(depth <= self.depth());
158        debug_assert!(byte_origin <= self.len());
159        let mut guard = UnwindGuard {
160            stack: self,
161            byte_origin,
162            depth,
163            armed: true,
164        };
165        let result = callback(guard.stack);
166        if result.is_ok() {
167            guard.armed = false;
168        }
169        result
170    }
171
172    /// Pops and then destroys a typed owner with its statically known padding.
173    fn destroy_typed<T, const PADDING: bool>(&mut self) {
174        drop(unsafe { self.pop::<T>(PADDING) });
175    }
176
177    /// Removes raw ownership and bytes before invoking any erased destructor.
178    fn destroy_raw(&mut self) {
179        let owner = self.raw_owners.pop().expect("raw owner exists");
180        debug_assert_eq!(owner.depth, self.depth());
181        self.owners.pop();
182        let offset = align_index(owner.align, owner.before);
183        let pointer = unsafe { self.buffer.as_mut_ptr().add(offset).cast::<u8>() };
184        self.buffer.truncate(owner.before);
185        if let Some(ty) = owner.ty {
186            unsafe { crate::owned_value::drop_raw(&ty, pointer) };
187        }
188    }
189
190    /// Returns the number of bytes currently on the stack.
191    #[must_use]
192    #[allow(clippy::len_without_is_empty)]
193    pub fn len(&self) -> usize {
194        self.buffer.len()
195    }
196
197    /// Pushes a value of type `T` onto the stack.
198    ///
199    /// The stack owns the value until it is popped using the correct type or destroyed.
200    /// `T: 'static` prevents owned destructors from retaining nonstatic borrows;
201    /// borrowed inputs must instead use explicitly borrowed adapters.
202    ///
203    /// # Type Parameters
204    ///
205    /// * `T`: The type of the value to push.
206    ///
207    /// # Examples
208    ///
209    /// ```
210    /// use cel_runtime::raw_stack::RawStack;
211    /// let mut stack = RawStack::with_base_alignment(align_of::<u32>());
212    /// let _ = stack.push(42u32);
213    /// ```
214    ///
215    /// A value whose destructor borrows nonstatic data cannot enter this owning stack:
216    ///
217    /// ```compile_fail,E0597
218    /// use cel_runtime::raw_stack::RawStack;
219    ///
220    /// /// Borrows text that must remain live until destruction.
221    /// struct BorrowedDrop<'a>(&'a str);
222    /// impl Drop for BorrowedDrop<'_> {
223    ///     /// Reads the borrowed text during destruction.
224    ///     fn drop(&mut self) {
225    ///         assert!(!self.0.is_empty());
226    ///     }
227    /// }
228    ///
229    /// let mut stack = RawStack::with_base_alignment(align_of::<BorrowedDrop<'static>>());
230    /// {
231    ///     let text = String::from("borrowed");
232    ///     stack.push(BorrowedDrop(&text));
233    /// }
234    /// drop(stack);
235    /// ```
236    ///
237    /// # Complexity
238    ///
239    /// The function has an amortized O(1) time complexity.
240    pub fn push<T: 'static>(&mut self, value: T) -> bool {
241        let len = self.buffer.len();
242        let aligned_index = align_index(align_of::<T>(), len);
243        let new_len = aligned_index + size_of::<T>();
244
245        self.owners.reserve(1);
246        self.buffer.reserve((new_len - len).max(1));
247        unsafe {
248            self.buffer.set_len(new_len);
249            if aligned_index - len > 0 {
250                // write a 1 in the first padding byte and 0 in the rest
251                self.buffer[len].write(1);
252                self.buffer[len + 1..aligned_index].fill(MaybeUninit::new(0));
253            }
254
255            std::ptr::write(
256                self.buffer.as_mut_ptr().add(aligned_index).cast::<T>(),
257                value,
258            );
259        }
260        let padding = aligned_index - len > 0;
261        self.owners.push(if padding {
262            Self::destroy_typed::<T, true>
263        } else {
264            Self::destroy_typed::<T, false>
265        });
266        padding
267    }
268
269    /// Pushes `size` raw bytes from `src`, aligned to `align`, using the same
270    /// padding/marker-byte bookkeeping as [`push`](Self::push).
271    ///
272    /// `src` is typed as `MaybeUninit<u8>` rather than `u8` because the bytes
273    /// being copied may include a source value's interior padding, which is
274    /// itself uninitialized — reading it through a `u8` pointer instead would
275    /// be undefined behavior even though this function never inspects the
276    /// bytes' values.
277    ///
278    /// - Precondition: `align` is a power of two.
279    /// - Postcondition: A raw-byte slot is registered; its bytes are not destroyed
280    ///   as a typed value unless upgraded with `own_top`.
281    /// - Complexity: Amortized O(size) time and space.
282    ///
283    /// # Safety
284    /// `src` must be valid for reads of `size` bytes, and must not overlap the
285    /// stack's internal buffer.
286    pub unsafe fn push_raw(
287        &mut self,
288        align: usize,
289        size: usize,
290        src: *const MaybeUninit<u8>,
291    ) -> bool {
292        debug_assert!(align.is_power_of_two());
293        let len = self.buffer.len();
294        let aligned_index = align_index(align, len);
295        let new_len = aligned_index + size;
296
297        self.owners.reserve(1);
298        self.raw_owners.reserve(1);
299        self.buffer.reserve((new_len - len).max(1));
300        unsafe {
301            self.buffer.set_len(new_len);
302            if aligned_index - len > 0 {
303                self.buffer[len].write(1);
304                self.buffer[len + 1..aligned_index].fill(MaybeUninit::new(0));
305            }
306            std::ptr::copy_nonoverlapping(src, self.buffer.as_mut_ptr().add(aligned_index), size);
307        }
308        self.owners.push(Self::destroy_raw);
309        self.raw_owners.push(RawOwner {
310            depth: self.depth(),
311            before: len,
312            align,
313            size,
314            ty: None,
315        });
316        aligned_index - len > 0
317    }
318
319    /// Reserves aligned space for `size` bytes (using the same padding/marker
320    /// bookkeeping as [`push`](Self::push)) and calls `write` with a pointer
321    /// to that space instead of copying from a source buffer.
322    ///
323    /// - Precondition: `align` is a power of two.
324    /// - Postcondition: `write` is called exactly once, before this method
325    ///   returns, with a pointer to `size` freshly-reserved bytes.
326    /// - Postcondition: Normal return registers a raw-byte slot; a panicking
327    ///   initializer registers no slot, leaving byte restoration to its caller.
328    /// - Complexity: Amortized O(size) plus initializer costs.
329    ///
330    /// # Safety
331    /// `write` must fully initialize all `size` bytes at the pointer it's
332    /// given before returning, except interior padding. If it panics, the caller
333    /// must guard partially initialized resources.
334    pub unsafe fn reserve_and_write(
335        &mut self,
336        align: usize,
337        size: usize,
338        write: impl FnOnce(*mut u8),
339    ) -> bool {
340        debug_assert!(align.is_power_of_two());
341        let len = self.buffer.len();
342        let aligned_index = align_index(align, len);
343        let new_len = aligned_index + size;
344
345        self.owners.reserve(1);
346        self.raw_owners.reserve(1);
347        self.buffer.reserve((new_len - len).max(1));
348        unsafe {
349            self.buffer.set_len(new_len);
350            if aligned_index - len > 0 {
351                self.buffer[len].write(1);
352                self.buffer[len + 1..aligned_index].fill(MaybeUninit::new(0));
353            }
354            write(self.buffer.as_mut_ptr().add(aligned_index).cast::<u8>());
355        }
356        self.owners.push(Self::destroy_raw);
357        self.raw_owners.push(RawOwner {
358            depth: self.depth(),
359            before: len,
360            align,
361            size,
362            ty: None,
363        });
364        aligned_index - len > 0
365    }
366
367    /// Copies `size` bytes starting at absolute buffer offset `offset` into `dst`.
368    ///
369    /// `dst` is typed as `MaybeUninit<u8>` rather than `u8` because the bytes
370    /// being copied may be a value's interior padding, which is itself
371    /// uninitialized — reading it through a `u8` pointer instead would be
372    /// undefined behavior even though this function never inspects the
373    /// bytes' values.
374    ///
375    /// # Safety
376    /// `offset..offset + size` must be within the currently-initialized buffer;
377    /// `dst` must be valid for writes of `size` bytes and must not overlap the
378    /// stack's internal buffer.
379    pub unsafe fn copy_from(&self, offset: usize, size: usize, dst: *mut MaybeUninit<u8>) {
380        debug_assert!(offset + size <= self.buffer.len());
381        unsafe {
382            std::ptr::copy_nonoverlapping(self.buffer.as_ptr().add(offset), dst, size);
383        }
384    }
385
386    /// Drops a value in place at absolute buffer offset `offset`, without
387    /// altering the stack's tracked length.
388    ///
389    /// # Safety
390    /// `offset` must point to a live, valid value; `run_drop` must correctly
391    /// run that value's destructor given a pointer to its start. Its ownership
392    /// entry must already have been removed with `release_top`.
393    pub unsafe fn drop_at(&mut self, offset: usize, run_drop: impl FnOnce(*mut u8)) {
394        unsafe { run_drop(self.buffer.as_mut_ptr().add(offset).cast::<u8>()) };
395    }
396
397    /// Reads a value at absolute buffer offset `offset`, given a callback that
398    /// receives a pointer to its bytes.
399    ///
400    /// # Safety
401    /// `offset` must point to a live, valid, properly-aligned value for the type the
402    /// caller will reinterpret it as; `read` must not retain the pointer beyond the
403    /// call. This byte operation does not transfer ownership; a caller moving out
404    /// a value must explicitly release its ownership entry before destruction or
405    /// stack cleanup can occur.
406    pub unsafe fn read_at<R>(&self, offset: usize, read: impl FnOnce(*const u8) -> R) -> R {
407        unsafe { read(self.buffer.as_ptr().add(offset).cast::<u8>()) }
408    }
409
410    /// Truncates the stack back to `new_len`, additionally stripping `padding`
411    /// bytes that preceded the removed region (scanned the same way
412    /// [`pop`](Self::pop) does).
413    ///
414    /// # Safety
415    /// No live (undropped) value may exist at or above `new_len`; the caller must
416    /// explicitly release all removed ownership entries first. This byte operation
417    /// does not infer ownership transfer, including for zero-sized values.
418    pub unsafe fn truncate_to(&mut self, new_len: usize, padding: bool) {
419        debug_assert!(new_len <= self.buffer.len());
420        let padding_count = if padding {
421            self.buffer[..new_len]
422                .iter()
423                .rev()
424                .take_while(|&x| unsafe { x.assume_init() == 0 })
425                .count()
426                + 1
427        } else {
428            0
429        };
430        self.buffer.truncate(new_len - padding_count);
431    }
432
433    /// Removes the top owner's bytes and metadata, then destroys its value in place.
434    ///
435    /// # Safety
436    /// The top `size` bytes (plus padding if `padding` is true) must be a
437    /// live, valid value; `run_drop` must correctly run its destructor given a
438    /// pointer to its start. The value must have a registered top ownership entry.
439    pub unsafe fn drop_sized(
440        &mut self,
441        size: usize,
442        padding: bool,
443        run_drop: impl FnOnce(*mut u8),
444    ) {
445        debug_assert!(size <= self.buffer.len());
446        let p = self.buffer.len() - size;
447        let pointer = unsafe { self.buffer.as_mut_ptr().add(p).cast::<u8>() };
448        self.release_top(1);
449        unsafe {
450            self.truncate_to(p, padding);
451            run_drop(pointer);
452        }
453    }
454
455    /// Repacks `sizes.len()` already-pushed values (currently at the absolute
456    /// byte offsets in `src_offsets`) into one contiguous, self-contained
457    /// region of `total_size` bytes starting at `dest_base`, placing element
458    /// `i` at `dest_base + dest_offsets[i]`. Adjusts the tracked length to
459    /// `dest_base + total_size` and returns whether leading padding was
460    /// inserted between `ambient_start` and `dest_base`.
461    ///
462    /// - Precondition: `src_offsets`, `dest_offsets`, and `sizes` have equal
463    ///   length; `dest_base >= ambient_start`; the source ranges are
464    ///   currently valid, initialized bytes.
465    ///
466    /// - Complexity: O(n) in `sizes.len()`.
467    ///
468    /// # Safety
469    /// The offsets and sizes must correctly describe the actual bytes in the
470    /// buffer; no two destination ranges may overlap. `src_offsets` and
471    /// `dest_offsets` must be in the same relative element order (element `i`'s
472    /// source and destination must both be the `i`-th non-overlapping range in
473    /// their respective layouts) — this method does not reorder elements, only
474    /// re-pads between them. Given that and `dest_base >= ambient_start`, each
475    /// element's destination start is guaranteed to be at or after every
476    /// earlier element's source end, which is what makes processing in reverse
477    /// index order below safe against clobbering not-yet-read source bytes.
478    /// The caller must release ownership entries for every moved value before
479    /// repacking, then register the resulting representation separately. This
480    /// byte operation never infers ownership transfer.
481    pub unsafe fn repack(
482        &mut self,
483        ambient_start: usize,
484        dest_base: usize,
485        total_size: usize,
486        src_offsets: &[usize],
487        dest_offsets: &[usize],
488        sizes: &[usize],
489    ) -> bool {
490        debug_assert!(dest_base >= ambient_start);
491        debug_assert_eq!(src_offsets.len(), sizes.len());
492        debug_assert_eq!(dest_offsets.len(), sizes.len());
493
494        let target_len = dest_base + total_size;
495        let current_len = self.buffer.len();
496        let grown_len = current_len.max(target_len);
497        unsafe {
498            if grown_len > current_len {
499                self.buffer.reserve(grown_len - current_len);
500                self.buffer.set_len(grown_len);
501            }
502            let base_ptr = self.buffer.as_mut_ptr();
503            // Process highest index first: each element's destination is
504            // provably at or after every earlier element's source end (see
505            // `# Safety` above), so this order never overwrites source bytes
506            // an earlier iteration still needs to read.
507            for i in (0..sizes.len()).rev() {
508                std::ptr::copy(
509                    base_ptr.add(src_offsets[i]),
510                    base_ptr.add(dest_base + dest_offsets[i]),
511                    sizes[i],
512                );
513            }
514            if dest_base > ambient_start {
515                self.buffer[ambient_start].write(1);
516                self.buffer[ambient_start + 1..dest_base].fill(MaybeUninit::new(0));
517            }
518            self.buffer.set_len(target_len);
519        }
520        dest_base > ambient_start
521    }
522
523    /// Pops a value of type `T` from the stack. Does not change the stack capacity.
524    ///
525    /// # Safety
526    ///
527    /// The type `T` must be the same type as the value on the top of the stack.
528    /// `padding` must match its push result, and the value must have a registered
529    /// ownership entry. Incorrect usage can lead to undefined behavior.
530    ///
531    /// # Type Parameters
532    ///
533    /// * `T`: The type of the value to pop.
534    ///
535    /// # Examples
536    ///
537    /// ```
538    /// use cel_runtime::raw_stack::RawStack;
539    /// let mut stack = RawStack::with_base_alignment(align_of::<u32>());
540    /// let padding = stack.push(100u32);
541    /// let value: u32 = unsafe { stack.pop(padding) };
542    /// ```
543    pub unsafe fn pop<T>(&mut self, padding: bool) -> T {
544        let p: usize = self.buffer.len() - size_of::<T>();
545        let result = unsafe { std::ptr::read(self.buffer.as_ptr().add(p).cast::<T>()) };
546        // count the number of trailing 0s in the buffer before the result
547        let padding_count = if padding {
548            self.buffer[..p]
549                .iter()
550                .rev()
551                .take_while(|&x| unsafe { x.assume_init() == 0 })
552                .count()
553                + 1
554        } else {
555            0
556        };
557        self.buffer.truncate(p - padding_count);
558        self.release_top(1);
559        result
560    }
561
562    /// Pops a value of type `T` from the stack and drops it.
563    ///
564    /// # Safety
565    ///
566    /// The type `T` must be the same type as the value on the top of the stack.
567    /// Incorrect usage can lead to undefined behavior.
568    ///
569    /// Removes ownership and bytes before invoking the value's destructor.
570    pub unsafe fn drop<T>(&mut self, padding: bool) {
571        unsafe { self.pop::<T>(padding) };
572    }
573}
574
575/* Test module */
576#[cfg(test)]
577mod tests {
578    use super::*;
579    use std::cell::RefCell;
580    use std::cmp::max;
581    use std::rc::Rc;
582
583    /// Records destruction order and optionally panics after recording its identifier.
584    struct Tracked {
585        id: usize,
586        drops: Rc<RefCell<Vec<usize>>>,
587        panic: bool,
588    }
589
590    impl Drop for Tracked {
591        /// Records this owner's destruction before optionally panicking.
592        fn drop(&mut self) {
593            self.drops.borrow_mut().push(self.id);
594            assert!(!self.panic, "destructor panic");
595        }
596    }
597
598    /// Pushes a tracked owner onto the stack.
599    fn track(
600        stack: &mut RawStack,
601        drops: &Rc<RefCell<Vec<usize>>>,
602        id: usize,
603        panic: bool,
604    ) -> bool {
605        stack.push(Tracked {
606            id,
607            drops: drops.clone(),
608            panic,
609        })
610    }
611
612    #[test]
613    /// Destroys live typed owners in reverse order across mixed alignments.
614    fn live_owners_drop_in_reverse_order() {
615        let drops = Rc::new(RefCell::new(Vec::new()));
616        {
617            let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
618            track(&mut stack, &drops, 1, false);
619            stack.push(1u8);
620            track(&mut stack, &drops, 2, false);
621        }
622        assert_eq!(*drops.borrow(), [2, 1]);
623    }
624
625    #[test]
626    /// Transfers a popped owner's destruction obligation to its caller.
627    fn popped_owner_is_not_dropped_twice() {
628        let drops = Rc::new(RefCell::new(Vec::new()));
629        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
630        let padding = track(&mut stack, &drops, 1, false);
631        let value: Tracked = unsafe { stack.pop(padding) };
632        drop(stack);
633        assert!(drops.borrow().is_empty());
634        drop(value);
635        assert_eq!(*drops.borrow(), [1]);
636    }
637
638    #[test]
639    /// Destroys the top owner and restores its preceding byte boundary, including ZSTs.
640    fn drop_top_preserves_prefix_and_padding() {
641        ZST_DROPS.with(|count| count.set(0));
642        let drops = Rc::new(RefCell::new(Vec::new()));
643        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
644        stack.push(42u8);
645        track(&mut stack, &drops, 1, false);
646        stack.push(ZeroSized);
647        let before_zero = stack.len();
648        stack.drop_top();
649        assert_eq!(stack.len(), before_zero);
650        assert_eq!(stack.depth(), 2);
651        ZST_DROPS.with(|count| assert_eq!(count.get(), 1));
652        stack.drop_top();
653        assert_eq!(stack.len(), 1);
654        assert_eq!(stack.depth(), 1);
655        assert_eq!(*drops.borrow(), [1]);
656        let prefix: u8 = unsafe { stack.pop(false) };
657        assert_eq!(prefix, 42);
658        drop(stack);
659        assert_eq!(*drops.borrow(), [1]);
660    }
661
662    #[test]
663    /// Removes a top owner's obligation before its destructor panics.
664    fn drop_top_panic_does_not_repeat_destruction() {
665        let drops = Rc::new(RefCell::new(Vec::new()));
666        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
667        track(&mut stack, &drops, 1, false);
668        let origin = stack.len();
669        track(&mut stack, &drops, 2, true);
670        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
671            stack.drop_top();
672        }));
673        assert!(result.is_err());
674        assert_eq!(stack.len(), origin);
675        assert_eq!(stack.depth(), 1);
676        assert_eq!(*drops.borrow(), [2]);
677        drop(stack);
678        assert_eq!(*drops.borrow(), [2, 1]);
679    }
680
681    thread_local! {
682        static ZST_DROPS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
683    }
684
685    /// Counts destruction of a value that occupies no stack bytes.
686    struct ZeroSized;
687
688    impl Drop for ZeroSized {
689        /// Increments the thread-local destruction count.
690        fn drop(&mut self) {
691            ZST_DROPS.with(|count| count.set(count.get() + 1));
692        }
693    }
694
695    #[test]
696    /// Distinguishes zero-sized owners by depth and preserves the caller's prefix.
697    fn scoped_unwind_preserves_zero_sized_prefix() {
698        ZST_DROPS.with(|count| count.set(0));
699        let mut stack = RawStack::with_base_alignment(align_of::<usize>());
700        stack.push(42usize);
701        stack.push(ZeroSized);
702        let origin = stack.len();
703        let depth = stack.depth();
704        let result: anyhow::Result<()> = stack.with_unwind(origin, depth, |stack| {
705            stack.push(ZeroSized);
706            stack.push(ZeroSized);
707            anyhow::bail!("callback failed")
708        });
709        assert!(result.is_err());
710        assert_eq!(stack.len(), origin);
711        assert_eq!(stack.depth(), depth);
712        ZST_DROPS.with(|count| assert_eq!(count.get(), 2));
713        drop(stack);
714        ZST_DROPS.with(|count| assert_eq!(count.get(), 3));
715    }
716
717    #[test]
718    /// Cleans a panicking callback's suffix without destroying the caller's owner.
719    fn callback_panic_cleans_suffix() {
720        let drops = Rc::new(RefCell::new(Vec::new()));
721        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
722        track(&mut stack, &drops, 1, false);
723        let origin = stack.len();
724        let depth = stack.depth();
725        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
726            let _: anyhow::Result<()> = stack.with_unwind(origin, depth, |stack| {
727                track(stack, &drops, 2, false);
728                panic!("callback panic")
729            });
730        }));
731        assert!(result.is_err());
732        assert_eq!(*drops.borrow(), [2]);
733        assert_eq!(stack.depth(), depth);
734        assert_eq!(stack.len(), origin);
735        drop(stack);
736        assert_eq!(*drops.borrow(), [2, 1]);
737    }
738
739    #[test]
740    /// Resumes remaining destruction after one destructor panics.
741    fn destructor_panic_resumes_cleanup() {
742        let drops = Rc::new(RefCell::new(Vec::new()));
743        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
744            let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
745            track(&mut stack, &drops, 1, false);
746            track(&mut stack, &drops, 2, true);
747            track(&mut stack, &drops, 3, false);
748        }));
749        assert!(result.is_err());
750        assert_eq!(*drops.borrow(), [3, 2, 1]);
751    }
752
753    #[test]
754    /// Upgrades copied raw storage into an erased destruction obligation.
755    fn raw_bytes_become_erased_owner() {
756        let drops = Rc::new(RefCell::new(Vec::new()));
757        let value = std::mem::ManuallyDrop::new(Tracked {
758            id: 1,
759            drops: drops.clone(),
760            panic: false,
761        });
762        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
763        unsafe {
764            stack.push_raw(
765                align_of::<Tracked>(),
766                size_of::<Tracked>(),
767                (&*value as *const Tracked).cast(),
768            );
769        }
770        stack.own_top(crate::value_type::ValueType::leaf::<Tracked>());
771        drop(stack);
772        assert_eq!(*drops.borrow(), [1]);
773    }
774
775    #[test]
776    /// Destroys an erased top owner without retaining its layout at the call site.
777    fn drop_top_destroys_erased_owner_and_padding() {
778        let drops = Rc::new(RefCell::new(Vec::new()));
779        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
780        stack.push(42u8);
781        unsafe {
782            stack.reserve_and_write(align_of::<Tracked>(), size_of::<Tracked>(), |pointer| {
783                pointer.cast::<Tracked>().write(Tracked {
784                    id: 1,
785                    drops: drops.clone(),
786                    panic: false,
787                });
788            });
789        }
790        stack.own_top(ValueType::leaf::<Tracked>());
791        stack.drop_top();
792        assert_eq!(stack.len(), 1);
793        assert_eq!(stack.depth(), 1);
794        assert_eq!(*drops.borrow(), [1]);
795        drop(stack);
796        assert_eq!(*drops.borrow(), [1]);
797    }
798
799    #[test]
800    /// Retains successful callback pushes until normal stack destruction.
801    fn successful_scope_keeps_owners() {
802        let drops = Rc::new(RefCell::new(Vec::new()));
803        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
804        let value = stack
805            .with_unwind(0, 0, |stack| {
806                track(stack, &drops, 1, false);
807                Ok(42)
808            })
809            .unwrap();
810        assert_eq!(value, 42);
811        assert_eq!(stack.depth(), 1);
812        assert!(drops.borrow().is_empty());
813        drop(stack);
814        assert_eq!(*drops.borrow(), [1]);
815    }
816
817    #[test]
818    /// Removes erased side metadata when transferring the top value to typed code.
819    fn typed_pop_transfers_erased_owner() {
820        let drops = Rc::new(RefCell::new(Vec::new()));
821        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
822        let padding = unsafe {
823            stack.reserve_and_write(align_of::<Tracked>(), size_of::<Tracked>(), |pointer| {
824                pointer.cast::<Tracked>().write(Tracked {
825                    id: 1,
826                    drops: drops.clone(),
827                    panic: false,
828                });
829            })
830        };
831        stack.own_top(ValueType::leaf::<Tracked>());
832        let value: Tracked = unsafe { stack.pop(padding) };
833        assert_eq!(stack.depth(), 0);
834        track(&mut stack, &drops, 2, false);
835        drop(stack);
836        assert_eq!(*drops.borrow(), [2]);
837        drop(value);
838        assert_eq!(*drops.borrow(), [2, 1]);
839    }
840
841    #[test]
842    /// Leaves no valid owner registered when raw initialization panics.
843    fn initializer_panic_registers_no_owner() {
844        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
845        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
846            stack.reserve_and_write(align_of::<Tracked>(), size_of::<Tracked>(), |_| {
847                panic!("uninitialized")
848            });
849        }));
850        assert!(result.is_err());
851        assert_eq!(stack.depth(), 0);
852        stack.unwind_to(0, 0);
853        assert_eq!(stack.len(), 0);
854    }
855
856    #[test]
857    /// Releases zero-sized ownership without destroying values or changing bytes.
858    fn release_top_preserves_bytes_and_releases_zero_sized_owner() {
859        ZST_DROPS.with(|count| count.set(0));
860        let mut stack = RawStack::with_base_alignment(align_of::<usize>());
861        stack.push(42usize);
862        stack.push(ZeroSized);
863        let origin = stack.len();
864        stack.release_top(1);
865        assert_eq!(stack.len(), origin);
866        assert_eq!(stack.depth(), 1);
867        let value: usize = unsafe { stack.pop(false) };
868        assert_eq!(value, 42);
869        drop(stack);
870        ZST_DROPS.with(|count| assert_eq!(count.get(), 0));
871    }
872
873    #[test]
874    /// Restores the caller's prefix after a suffix destructor panics.
875    fn scoped_destructor_panic_restores_prefix() {
876        let drops = Rc::new(RefCell::new(Vec::new()));
877        let mut stack = RawStack::with_base_alignment(align_of::<Tracked>());
878        track(&mut stack, &drops, 1, false);
879        let origin = stack.len();
880        let depth = stack.depth();
881        track(&mut stack, &drops, 2, false);
882        track(&mut stack, &drops, 3, true);
883        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
884            stack.unwind_to(origin, depth);
885        }));
886        assert!(result.is_err());
887        assert_eq!(*drops.borrow(), [3, 2]);
888        assert_eq!(stack.len(), origin);
889        assert_eq!(stack.depth(), depth);
890        drop(stack);
891        assert_eq!(*drops.borrow(), [3, 2, 1]);
892    }
893
894    #[test]
895    fn push_pop_u32() {
896        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
897        let padding = stack.push(10u32);
898        let result: u32 = unsafe { stack.pop(padding) };
899        assert_eq!(result, 10);
900    }
901
902    #[test]
903    fn multiple_push_pop() {
904        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
905        let padding1 = stack.push(1u32);
906        let padding2 = stack.push(2u32);
907        let padding3 = stack.push(3u32);
908        let v3: u32 = unsafe { stack.pop(padding3) };
909        let v2: u32 = unsafe { stack.pop(padding2) };
910        let v1: u32 = unsafe { stack.pop(padding1) };
911        assert_eq!(v1, 1);
912        assert_eq!(v2, 2);
913        assert_eq!(v3, 3);
914    }
915
916    #[test]
917    fn push_pop_different_types() {
918        let mut stack = RawStack::with_base_alignment(max(align_of::<u32>(), align_of::<f64>()));
919        let padding1 = stack.push(42u32);
920        let padding2 = stack.push(42.14f64);
921        let value_f: f64 = unsafe { stack.pop(padding2) };
922        let value_u: u32 = unsafe { stack.pop(padding1) };
923        assert_eq!(value_f, 42.14);
924        assert_eq!(value_u, 42);
925    }
926
927    #[test]
928    fn len_reflects_pushed_bytes() {
929        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
930        assert_eq!(stack.len(), 0);
931        let _ = stack.push(7u32);
932        assert_eq!(stack.len(), size_of::<u32>());
933    }
934
935    #[test]
936    fn push_raw_round_trips_like_push() {
937        let mut stack = RawStack::with_base_alignment(align_of::<f64>());
938        let padding1 = stack.push(1u8);
939        let value = 42.14f64;
940        let padding2 = unsafe {
941            stack.push_raw(
942                align_of::<f64>(),
943                size_of::<f64>(),
944                (&value as *const f64).cast::<MaybeUninit<u8>>(),
945            )
946        };
947        let popped: f64 = unsafe { stack.pop(padding2) };
948        assert_eq!(popped, 42.14);
949        let popped_u8: u8 = unsafe { stack.pop(padding1) };
950        assert_eq!(popped_u8, 1);
951    }
952
953    #[test]
954    fn push_raw_padding_matches_typed_push() {
955        let mut stack_a = RawStack::with_base_alignment(align_of::<f64>());
956        let _ = stack_a.push(1u8);
957        let padding_typed = stack_a.push(2.5f64);
958
959        let mut stack_b = RawStack::with_base_alignment(align_of::<f64>());
960        let _ = stack_b.push(1u8);
961        let value = 2.5f64;
962        let padding_raw = unsafe {
963            stack_b.push_raw(
964                align_of::<f64>(),
965                size_of::<f64>(),
966                (&value as *const f64).cast::<MaybeUninit<u8>>(),
967            )
968        };
969        assert_eq!(padding_typed, padding_raw);
970    }
971
972    #[test]
973    fn reserve_and_write_matches_push_raw_padding_and_bytes() {
974        let mut stack_a = RawStack::with_base_alignment(align_of::<f64>());
975        let _ = stack_a.push(1u8);
976        let value = 2.5f64;
977        let padding_a = unsafe {
978            stack_a.push_raw(
979                align_of::<f64>(),
980                size_of::<f64>(),
981                (&value as *const f64).cast::<MaybeUninit<u8>>(),
982            )
983        };
984
985        let mut stack_b = RawStack::with_base_alignment(align_of::<f64>());
986        let _ = stack_b.push(1u8);
987        let padding_b = unsafe {
988            stack_b.reserve_and_write(align_of::<f64>(), size_of::<f64>(), |dst| {
989                std::ptr::write(dst.cast::<f64>(), 2.5f64);
990            })
991        };
992
993        assert_eq!(padding_a, padding_b);
994        let popped_a: f64 = unsafe { stack_a.pop(padding_a) };
995        let popped_b: f64 = unsafe { stack_b.pop(padding_b) };
996        assert_eq!(popped_a, popped_b);
997    }
998
999    #[test]
1000    fn reserve_and_write_runs_write_exactly_once_with_correct_size() {
1001        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
1002        let mut call_count = 0;
1003        let padding = unsafe {
1004            stack.reserve_and_write(align_of::<u32>(), size_of::<u32>(), |dst| {
1005                call_count += 1;
1006                std::ptr::write(dst.cast::<u32>(), 42);
1007            })
1008        };
1009        assert_eq!(call_count, 1);
1010        let result: u32 = unsafe { stack.pop(padding) };
1011        assert_eq!(result, 42);
1012    }
1013
1014    #[test]
1015    fn copy_from_reads_bytes_at_offset() {
1016        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
1017        let _ = stack.push(10u32);
1018        let _ = stack.push(20u32);
1019        let mut buf = [0u8; 4];
1020        unsafe { stack.copy_from(0, 4, buf.as_mut_ptr().cast::<MaybeUninit<u8>>()) };
1021        assert_eq!(u32::from_ne_bytes(buf), 10);
1022    }
1023
1024    #[test]
1025    fn read_at_gives_a_pointer_to_the_value_without_copying() {
1026        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
1027        let _ = stack.push(10u32);
1028        let _ = stack.push(20u32);
1029        let first: u32 = unsafe { stack.read_at(0, |ptr| *ptr.cast::<u32>()) };
1030        let second: u32 = unsafe { stack.read_at(4, |ptr| *ptr.cast::<u32>()) };
1031        assert_eq!(first, 10);
1032        assert_eq!(second, 20);
1033    }
1034
1035    #[test]
1036    fn drop_at_runs_destructor_without_changing_length() {
1037        use std::sync::Arc;
1038        use std::sync::atomic::{AtomicUsize, Ordering};
1039
1040        struct DropCounter(Arc<AtomicUsize>);
1041        impl Drop for DropCounter {
1042            fn drop(&mut self) {
1043                self.0.fetch_add(1, Ordering::SeqCst);
1044            }
1045        }
1046
1047        let count = Arc::new(AtomicUsize::new(0));
1048        let mut stack = RawStack::with_base_alignment(align_of::<DropCounter>());
1049        let _ = stack.push(DropCounter(count.clone()));
1050        let len_before = stack.len();
1051        stack.release_top(1);
1052        unsafe {
1053            stack.drop_at(0, |ptr| std::ptr::drop_in_place(ptr.cast::<DropCounter>()));
1054        }
1055        assert_eq!(count.load(Ordering::SeqCst), 1);
1056        assert_eq!(stack.len(), len_before);
1057    }
1058
1059    #[test]
1060    fn truncate_to_strips_recorded_padding() {
1061        let mut stack = RawStack::with_base_alignment(align_of::<f64>());
1062        let _ = stack.push(1u8);
1063        let padding = stack.push(2.5f64); // padding == true: 7 bytes inserted before the f64
1064        let len_with_value = stack.len();
1065        stack.release_top(1);
1066        unsafe { stack.truncate_to(len_with_value - size_of::<f64>(), padding) };
1067        assert_eq!(stack.len(), 1); // back to just the u8
1068    }
1069
1070    #[test]
1071    fn drop_sized_combines_drop_at_and_truncate_to() {
1072        use std::sync::Arc;
1073        use std::sync::atomic::{AtomicUsize, Ordering};
1074
1075        struct DropCounter(Arc<AtomicUsize>);
1076        impl Drop for DropCounter {
1077            fn drop(&mut self) {
1078                self.0.fetch_add(1, Ordering::SeqCst);
1079            }
1080        }
1081
1082        let count = Arc::new(AtomicUsize::new(0));
1083        let mut stack = RawStack::with_base_alignment(align_of::<DropCounter>());
1084        let _ = stack.push(1u8);
1085        let padding = stack.push(DropCounter(count.clone()));
1086        unsafe {
1087            stack.drop_sized(size_of::<DropCounter>(), padding, |ptr| {
1088                std::ptr::drop_in_place(ptr.cast::<DropCounter>())
1089            });
1090        }
1091        assert_eq!(count.load(Ordering::SeqCst), 1);
1092        assert_eq!(stack.len(), 1);
1093    }
1094
1095    #[test]
1096    fn repack_moves_elements_to_ideal_offsets_and_reports_padding() {
1097        // Ambient layout: [u8 @0][pad][u32 @4][u8 @8] — u8 then u32 then u8, each
1098        // pushed with ordinary alignment relative to a 1-byte ambient start.
1099        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
1100        let ambient_start = stack.len(); // 0
1101        let _ = stack.push(0xAAu8); // element 0: ambient offset 0
1102        let _p1 = stack.push(0xBBBB_BBBBu32); // element 1: ambient offset 4 (1 byte padded to 4)
1103        let _p2 = stack.push(0xCCu8); // element 2: ambient offset 8
1104
1105        // Ideal (self-contained) layout for (u8, u32, u8) from a zero base:
1106        // offset 0 (u8), offset 4 (u32, aligned up from 1), offset 8 (u8) -> total 9,
1107        // rounded to the tuple's own max align (4) -> total_size 12.
1108        let src_offsets = [0usize, 4, 8];
1109        let dest_offsets = [0usize, 4, 8];
1110        let sizes = [1usize, 4, 1];
1111        let total_size = 12usize;
1112        let dest_base = 0usize; // ambient_start (0) is already 4-aligned
1113
1114        stack.release_top(3);
1115        let padding = unsafe {
1116            stack.repack(
1117                ambient_start,
1118                dest_base,
1119                total_size,
1120                &src_offsets,
1121                &dest_offsets,
1122                &sizes,
1123            )
1124        };
1125        assert!(
1126            !padding,
1127            "ambient_start was already aligned; no leading pad expected"
1128        );
1129        assert_eq!(stack.len(), dest_base + total_size);
1130
1131        let mut a = [0u8; 1];
1132        let mut b = [0u8; 4];
1133        let mut c = [0u8; 1];
1134        unsafe {
1135            stack.copy_from(dest_base, 1, a.as_mut_ptr().cast::<MaybeUninit<u8>>());
1136            stack.copy_from(dest_base + 4, 4, b.as_mut_ptr().cast::<MaybeUninit<u8>>());
1137            stack.copy_from(dest_base + 8, 1, c.as_mut_ptr().cast::<MaybeUninit<u8>>());
1138        }
1139        assert_eq!(a[0], 0xAA);
1140        assert_eq!(u32::from_ne_bytes(b), 0xBBBB_BBBB);
1141        assert_eq!(c[0], 0xCC);
1142    }
1143
1144    #[test]
1145    fn repack_shifts_right_without_corrupting_unread_source_bytes() {
1146        // A misaligned ambient_start forces dest_base > ambient_start, which
1147        // means the destination of the *first* tuple element can land inside
1148        // the *source* range of a *later*, not-yet-copied element. Processing
1149        // elements low-index-first would silently corrupt that later element's
1150        // bytes before they're read; this test fails under that ordering and
1151        // passes under the correct (reverse) ordering.
1152        let mut stack = RawStack::with_base_alignment(align_of::<u32>());
1153        let _ = stack.push(0xFFu8); // sentinel, ambient offset 0, before the tuple
1154        let ambient_start = stack.len(); // 1: misaligned relative to u32's 4-byte align
1155        let _ = stack.push(0xDDu8); // element 0: ambient offset 1
1156        let _ = stack.push(0x1122_3344u32); // element 1: ambient offset 4 (3 bytes padded)
1157
1158        // Ideal (u8, u32) layout from zero: offset 0 (u8), offset 4 (u32) -> total 8.
1159        let src_offsets = [1usize, 4];
1160        let dest_offsets = [0usize, 4];
1161        let sizes = [1usize, 4];
1162        let total_size = 8usize;
1163        let dest_base = 4usize; // align_index(4, ambient_start=1) == 4, so this shifts right
1164
1165        stack.release_top(2);
1166        let padding = unsafe {
1167            stack.repack(
1168                ambient_start,
1169                dest_base,
1170                total_size,
1171                &src_offsets,
1172                &dest_offsets,
1173                &sizes,
1174            )
1175        };
1176        assert!(
1177            padding,
1178            "ambient_start (1) is not 4-aligned; a leading pad is expected"
1179        );
1180        assert_eq!(stack.len(), dest_base + total_size);
1181
1182        let mut sentinel = [0u8; 1];
1183        let mut a = [0u8; 1];
1184        let mut b = [0u8; 4];
1185        unsafe {
1186            stack.copy_from(0, 1, sentinel.as_mut_ptr().cast::<MaybeUninit<u8>>());
1187            stack.copy_from(dest_base, 1, a.as_mut_ptr().cast::<MaybeUninit<u8>>());
1188            stack.copy_from(dest_base + 4, 4, b.as_mut_ptr().cast::<MaybeUninit<u8>>());
1189        }
1190        assert_eq!(
1191            sentinel[0], 0xFF,
1192            "bytes before the tuple must be untouched"
1193        );
1194        assert_eq!(a[0], 0xDD);
1195        assert_eq!(
1196            u32::from_ne_bytes(b),
1197            0x1122_3344,
1198            "the u32's bytes must survive the repack uncorrupted"
1199        );
1200    }
1201}