Skip to main content

trame_runtime/verified/
mod.rs

1//! Verified implementations of all of trame's runtime traits: storage is heavily bounded, all
2//! operations are checked, we track a lot of things.
3
4#[cfg(not(creusot))]
5use core::cell::UnsafeCell;
6use std::alloc::Layout;
7
8#[cfg(creusot)]
9use creusot_std::model::DeepModel;
10
11mod byte_range;
12use byte_range::{ByteRangeError, ByteRangeTracker, Range};
13
14use crate::{IArena, IField, IHeap, IPtr, IRuntime, IShape, IShapeStore, IStructType, Idx};
15
16/// A runtime that verifies all operations
17pub struct VRuntime;
18
19#[cfg(not(creusot))]
20struct VShapeStoreCell(UnsafeCell<VShapeStore>);
21
22// SAFETY: Verified runtime is single-threaded. Concurrent access is undefined behavior.
23#[cfg(not(creusot))]
24unsafe impl Sync for VShapeStoreCell {}
25
26#[cfg(not(creusot))]
27static VSHAPE_STORE: VShapeStoreCell = VShapeStoreCell(UnsafeCell::new(VShapeStore::new()));
28
29#[cfg(not(creusot))]
30pub fn vshape_store() -> &'static VShapeStore {
31    unsafe { &*VSHAPE_STORE.0.get() }
32}
33
34/// Register a new verified shape in the global store.
35#[cfg(not(creusot))]
36pub fn vshape_register(shape: VShapeDef) -> VShapeHandle {
37    unsafe { (&mut *VSHAPE_STORE.0.get()).add(shape) }
38}
39
40/// View a previously registered shape from the global store.
41#[cfg(not(creusot))]
42pub fn vshape_view(handle: VShapeHandle) -> VShapeView<'static, VShapeStore> {
43    unsafe { (&*VSHAPE_STORE.0.get()).view(handle) }
44}
45
46/// Reset the global shape store. For testing only.
47///
48/// # Safety
49/// Caller must ensure no VShapeView or VShapeHandle from this store is in use.
50#[cfg(not(creusot))]
51pub unsafe fn vshape_store_reset() {
52    unsafe { *VSHAPE_STORE.0.get() = VShapeStore::new() }
53}
54
55#[cfg(creusot)]
56pub fn vshape_store() -> &'static VShapeStore {
57    panic!("vshape_store is unavailable under creusot")
58}
59
60#[cfg(creusot)]
61pub fn vshape_register(_shape: VShapeDef) -> VShapeHandle {
62    panic!("vshape_register is unavailable under creusot")
63}
64
65#[cfg(creusot)]
66pub fn vshape_view(_handle: VShapeHandle) -> VShapeView<'static, VShapeStore> {
67    panic!("vshape_view is unavailable under creusot")
68}
69
70#[cfg(creusot)]
71pub unsafe fn vshape_store_reset() {
72    // no-op under creusot
73}
74
75impl IRuntime for VRuntime {
76    type Shape = VShapeView<'static, VShapeStore>;
77    type Heap = VHeap<Self::Shape>;
78    type Arena<T> = VArena<T, MAX_VARENA_SLOTS>;
79
80    fn heap() -> Self::Heap {
81        VHeap::new()
82    }
83
84    fn arena<T>() -> Self::Arena<T> {
85        VArena::new()
86    }
87}
88
89// ==================================================================
90// Shape
91// ==================================================================
92
93/// Maximum number of shapes in a store (for bounded verification).
94pub const MAX_SHAPES_PER_STORE: usize = 8;
95
96/// Maximum number of fields in a struct (for bounded verification).
97pub const MAX_FIELDS_PER_STRUCT: usize = 8;
98
99/// A handle to a shape in a DynShapeStore.
100///
101/// This is just an index into the store's shape array.
102#[cfg_attr(creusot, derive(DeepModel))]
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct VShapeHandle(pub u8);
105
106/// A synthetic field for Kani verification.
107///
108/// Fields reference their type's shape by handle (index) rather than
109/// containing the shape directly. This avoids recursive type definitions.
110#[cfg_attr(creusot, derive(DeepModel))]
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct VFieldDef {
113    /// Byte offset of this field within the struct.
114    pub offset: usize,
115
116    /// Handle to the shape of this field's type.
117    pub shape_handle: VShapeHandle,
118}
119
120/// A synthetic struct type for Kani verification.
121#[cfg_attr(creusot, derive(DeepModel))]
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct VStructDef {
124    /// Number of fields.
125    pub field_count: u8,
126
127    /// Field information (only first `field_count` entries are valid).
128    pub fields: [VFieldDef; MAX_FIELDS_PER_STRUCT],
129}
130
131/// A bounded shape definition for Kani verification.
132///
133/// Unlike `facet_core::Shape` which uses static references and can be recursive,
134/// these shapes are bounded and can implement `kani::Arbitrary`.
135///
136/// Shape definitions live in a `DynShapeStore` and are referenced by handle.
137#[cfg_attr(creusot, derive(DeepModel))]
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct VShapeDef {
140    /// Layout of this type.
141    pub layout: VLayout,
142    ///
143    /// Type-specific information.
144    pub def: VDef,
145}
146
147/// Type-specific definition for verified shapes.
148#[cfg_attr(creusot, derive(DeepModel))]
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum VDef {
151    /// A scalar type (no internal structure to track).
152    Scalar,
153    /// A struct with indexed fields.
154    Struct(VStructDef),
155    // TODO: Enum, Option, Result, List, Map, etc.
156}
157
158/// A store of DynShape definitions.
159///
160/// Shapes are stored in an array and referenced by index (DynShapeHandle).
161/// This allows shapes to reference other shapes (nested structs) without
162/// creating recursive type definitions.
163#[cfg_attr(creusot, derive(DeepModel))]
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct VShapeStore {
166    /// Number of shapes in the store.
167    pub shape_count: u8,
168
169    /// Shape definitions (only first `shape_count` entries are valid).
170    pub shapes: [VShapeDef; MAX_SHAPES_PER_STORE],
171}
172
173/// A view into a shape, borrowing from a store.
174///
175/// This is the common currency for working with shapes in the verified runtime.
176#[derive(Clone, Copy)]
177pub struct VShapeView<'a, Store: IShapeStore> {
178    pub store: &'a Store,
179    pub handle: Store::Handle,
180}
181
182impl VShapeStore {
183    /// Create a new empty store.
184    pub const fn new() -> Self {
185        Self {
186            shape_count: 0,
187            shapes: [VShapeDef {
188                layout: VLayout::new::<()>(),
189                def: VDef::Scalar,
190            }; MAX_SHAPES_PER_STORE],
191        }
192    }
193
194    /// Add a shape to the store and return its handle.
195    pub fn add(&mut self, shape: VShapeDef) -> VShapeHandle {
196        assert!(
197            (self.shape_count as usize) < MAX_SHAPES_PER_STORE,
198            "shape store full"
199        );
200        let handle = VShapeHandle(self.shape_count);
201        self.shapes[self.shape_count as usize] = shape;
202        self.shape_count += 1;
203        handle
204    }
205
206    /// Get a shape definition by handle.
207    pub fn get_def(&self, handle: VShapeHandle) -> &VShapeDef {
208        assert!(
209            (handle.0 as usize) < self.shape_count as usize,
210            "invalid handle"
211        );
212        &self.shapes[handle.0 as usize]
213    }
214
215    /// Get a view into a shape.
216    pub fn view(&self, handle: VShapeHandle) -> VShapeView<'_, Self> {
217        VShapeView {
218            store: self,
219            handle,
220        }
221    }
222}
223
224impl Default for VShapeStore {
225    fn default() -> Self {
226        Self::new()
227    }
228}
229
230impl IShapeStore for VShapeStore {
231    type Handle = VShapeHandle;
232    type View<'a>
233        = VShapeView<'a, VShapeStore>
234    where
235        Self: 'a;
236
237    fn get<'a>(&'a self, handle: Self::Handle) -> Self::View<'a> {
238        self.view(handle)
239    }
240}
241
242impl IShapeStore for &'static VShapeStore {
243    type Handle = VShapeHandle;
244    type View<'a>
245        = VShapeView<'a, VShapeStore>
246    where
247        Self: 'a;
248
249    fn get<'a>(&'a self, handle: Self::Handle) -> Self::View<'a> {
250        VShapeView {
251            store: *self,
252            handle,
253        }
254    }
255}
256
257/// A field view that borrows from a store.
258#[derive(Clone, Copy)]
259pub struct VFieldView<'a> {
260    pub store: &'a VShapeStore,
261    pub def: &'a VFieldDef,
262}
263
264/// A struct type view that borrows from a store.
265#[derive(Clone, Copy)]
266pub struct VStructView<'a> {
267    pub store: &'a VShapeStore,
268    pub def: &'a VStructDef,
269}
270
271impl<'a> PartialEq for VShapeView<'a, VShapeStore> {
272    fn eq(&self, other: &Self) -> bool {
273        // Two views are equal if they point to the same store and handle.
274        // We compare store pointers and handle values.
275        core::ptr::eq(self.store, other.store) && self.handle == other.handle
276    }
277}
278
279impl<'a> Eq for VShapeView<'a, VShapeStore> {}
280
281impl<'a> IShape for VShapeView<'a, VShapeStore> {
282    type StructType = VStructView<'a>;
283    type Field = VFieldView<'a>;
284
285    #[inline]
286    fn layout(&self) -> Option<Layout> {
287        #[cfg(creusot)]
288        {
289            Some(self.store.get_def(self.handle).layout.to_layout())
290        }
291        #[cfg(not(creusot))]
292        {
293            Some(self.store.get_def(self.handle).layout)
294        }
295    }
296
297    #[inline]
298    fn is_struct(&self) -> bool {
299        matches!(self.store.get_def(self.handle).def, VDef::Struct(_))
300    }
301
302    #[inline]
303    fn as_struct(&self) -> Option<Self::StructType> {
304        match &self.store.get_def(self.handle).def {
305            VDef::Struct(s) => Some(VStructView {
306                store: self.store,
307                def: s,
308            }),
309            _ => None,
310        }
311    }
312}
313
314impl<'a> IStructType for VStructView<'a> {
315    type Field = VFieldView<'a>;
316
317    #[inline]
318    fn field_count(&self) -> usize {
319        self.def.field_count as usize
320    }
321
322    #[inline]
323    fn field(&self, idx: usize) -> Option<Self::Field> {
324        if idx < self.def.field_count as usize {
325            Some(VFieldView {
326                store: self.store,
327                def: &self.def.fields[idx],
328            })
329        } else {
330            None
331        }
332    }
333}
334
335impl<'a> IField for VFieldView<'a> {
336    type Shape = VShapeView<'a, VShapeStore>;
337
338    #[inline]
339    fn offset(&self) -> usize {
340        self.def.offset
341    }
342
343    #[inline]
344    fn shape(&self) -> Self::Shape {
345        // Look up the field's shape in the store by handle
346        self.store.view(self.def.shape_handle)
347    }
348}
349
350// ============================================================================
351// Constructors
352// ============================================================================
353
354impl VShapeDef {
355    /// Create a scalar shape with the given layout.
356    pub const fn scalar(layout: VLayout) -> Self {
357        Self {
358            layout,
359            def: VDef::Scalar,
360        }
361    }
362
363    /// Create a struct shape with the given fields.
364    ///
365    /// The `field_shapes` parameter provides the shape handle for each field.
366    /// Use `store.get_def(handle).layout` to get layouts for size calculation.
367    pub fn struct_with_fields(store: &VShapeStore, fields: &[(usize, VShapeHandle)]) -> Self {
368        assert!(fields.len() <= MAX_FIELDS_PER_STRUCT, "too many fields");
369
370        // Calculate overall layout from fields
371        let mut size = 0usize;
372        let mut align = 1usize;
373
374        for &(offset, shape_handle) in fields {
375            let field_layout = store.get_def(shape_handle).layout;
376            align = align.max(field_layout.align());
377            let field_end = offset + field_layout.size();
378            size = size.max(field_end);
379        }
380
381        // Round up size to alignment
382        size = (size + align - 1) & !(align - 1);
383
384        let layout = VLayout::from_size_align(size, align).expect("valid layout");
385
386        let mut field_array = [VFieldDef {
387            offset: 0,
388            shape_handle: VShapeHandle(0),
389        }; MAX_FIELDS_PER_STRUCT];
390        for (i, &(offset, shape_handle)) in fields.iter().enumerate() {
391            field_array[i] = VFieldDef {
392                offset,
393                shape_handle,
394            };
395        }
396
397        Self {
398            layout,
399            def: VDef::Struct(VStructDef {
400                field_count: fields.len() as u8,
401                fields: field_array,
402            }),
403        }
404    }
405}
406
407impl VFieldDef {
408    /// Create a new field definition.
409    pub const fn new(offset: usize, shape_handle: VShapeHandle) -> Self {
410        Self {
411            offset,
412            shape_handle,
413        }
414    }
415}
416
417// ============================================================================
418// Arbitrary for Kani
419// ============================================================================
420
421#[cfg(kani)]
422impl kani::Arbitrary for VShapeDef {
423    fn any() -> Self {
424        let is_struct: bool = kani::any();
425
426        if is_struct {
427            // Struct with 1-4 fields
428            let field_count: u8 = kani::any();
429            kani::assume(field_count > 0 && field_count <= 4);
430
431            let mut fields = [VFieldDef {
432                offset: 0,
433                shape_handle: VShapeHandle(0),
434            }; MAX_FIELDS_PER_STRUCT];
435
436            let mut offset = 0usize;
437            for i in 0..(field_count as usize) {
438                let field_size: usize = kani::any();
439                kani::assume(field_size > 0 && field_size <= 8);
440
441                // For arbitrary shapes, fields point to shape 0 (a placeholder)
442                // In real use, the store would be populated first
443                fields[i] = VFieldDef::new(offset, VShapeHandle(0));
444                offset += field_size;
445            }
446
447            kani::assume(offset <= 64);
448
449            let layout = VLayout::from_size_align(offset, 1).unwrap();
450
451            VShapeDef {
452                layout,
453                def: VDef::Struct(VStructDef {
454                    field_count,
455                    fields,
456                }),
457            }
458        } else {
459            // Scalar
460            let size: usize = kani::any();
461            let align_pow: u8 = kani::any();
462            kani::assume(size <= 64);
463            kani::assume(align_pow <= 3);
464            let align = 1usize << align_pow;
465            kani::assume(size == 0 || size % align == 0);
466
467            let layout = VLayout::from_size_align(size, align).unwrap();
468            VShapeDef::scalar(layout)
469        }
470    }
471}
472
473/// Generate an arbitrary shape store with nested struct support.
474#[cfg(kani)]
475impl kani::Arbitrary for VShapeStore {
476    fn any() -> Self {
477        let mut store = VShapeStore::new();
478
479        // First, add some scalar shapes that can be used by struct fields
480        let num_scalars: u8 = kani::any();
481        kani::assume(num_scalars >= 1 && num_scalars <= 4);
482
483        for _ in 0..num_scalars {
484            let size: usize = kani::any();
485            kani::assume(size > 0 && size <= 8);
486            let layout = VLayout::from_size_align(size, 1).unwrap();
487            store.add(VShapeDef::scalar(layout));
488        }
489
490        // Then optionally add a struct that uses those scalars as fields
491        let add_struct: bool = kani::any();
492        if add_struct {
493            let field_count: u8 = kani::any();
494            kani::assume(field_count > 0 && field_count <= 4);
495
496            let mut fields = [VFieldDef {
497                offset: 0,
498                shape_handle: VShapeHandle(0),
499            }; MAX_FIELDS_PER_STRUCT];
500
501            let mut offset = 0usize;
502            for i in 0..(field_count as usize) {
503                // Pick a random scalar shape for this field
504                let shape_idx: u8 = kani::any();
505                kani::assume(shape_idx < num_scalars);
506
507                let field_layout = store.get_def(VShapeHandle(shape_idx)).layout;
508                fields[i] = VFieldDef::new(offset, VShapeHandle(shape_idx));
509                offset += field_layout.size();
510            }
511
512            kani::assume(offset <= 64);
513
514            let layout = VLayout::from_size_align(offset, 1).unwrap();
515            store.add(VShapeDef {
516                layout,
517                def: VDef::Struct(VStructDef {
518                    field_count,
519                    fields,
520                }),
521            });
522        }
523
524        store
525    }
526}
527
528// ==================================================================
529// Pointer (Verified)
530// ==================================================================
531
532/// Fat pointer for verified heap operations.
533///
534/// Contains allocation metadata for bounds checking without borrowing the heap.
535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
536pub struct VPtr {
537    /// Which allocation this points into.
538    pub alloc_id: u8,
539    /// Current byte offset within the allocation.
540    pub offset: u32,
541    /// Total size of the allocation (for bounds checking).
542    pub size: u32,
543}
544
545impl IPtr for VPtr {
546    #[inline]
547    unsafe fn byte_add(self, n: usize) -> Self {
548        VPtr::offset(self, n)
549    }
550}
551
552impl VPtr {
553    /// Create a new pointer to the start of an allocation.
554    #[inline]
555    pub const fn new(alloc_id: u8, size: u32) -> Self {
556        Self {
557            alloc_id,
558            offset: 0,
559            size,
560        }
561    }
562
563    /// Compute a new pointer at an offset from this one.
564    ///
565    /// # Panics
566    /// Panics if the resulting offset would exceed the allocation size.
567    #[inline]
568    pub fn offset(self, n: usize) -> Self {
569        let new_offset = self.offset.checked_add(n as u32).expect("offset overflow");
570        #[cfg(not(creusot))]
571        assert!(
572            new_offset <= self.size,
573            "pointer arithmetic out of bounds: offset {} + {} = {} > size {}",
574            self.offset,
575            n,
576            new_offset,
577            self.size
578        );
579        #[cfg(creusot)]
580        {
581            if new_offset > self.size {
582                panic!("pointer arithmetic out of bounds");
583            }
584        }
585        Self {
586            alloc_id: self.alloc_id,
587            offset: new_offset,
588            size: self.size,
589        }
590    }
591
592    /// Returns the current offset within the allocation.
593    #[inline]
594    pub const fn offset_bytes(self) -> usize {
595        self.offset as usize
596    }
597
598    /// Returns the allocation ID.
599    #[inline]
600    pub const fn alloc_id(self) -> u8 {
601        self.alloc_id
602    }
603
604    /// Returns the total size of the allocation.
605    #[inline]
606    pub const fn alloc_size(self) -> usize {
607        self.size as usize
608    }
609
610    /// Check if this pointer is at the start of its allocation.
611    #[inline]
612    pub const fn is_at_start(self) -> bool {
613        self.offset == 0
614    }
615
616    /// Returns the number of bytes remaining from this offset to end of allocation.
617    #[inline]
618    pub const fn remaining(self) -> usize {
619        (self.size - self.offset) as usize
620    }
621}
622
623// ==================================================================
624// Heap
625// ==================================================================
626
627/// Maximum number of allocations tracked by VHeap.
628pub const MAX_VHEAP_ALLOCS: usize = 8;
629
630/// Error from heap operations.
631#[derive(Debug, Clone, Copy, PartialEq, Eq)]
632pub enum VHeapError {
633    /// Tried to access a freed allocation.
634    AllocationFreed,
635    /// Shape mismatch on dealloc or drop.
636    ShapeMismatch,
637    /// Pointer not at allocation start.
638    NotAtStart,
639    /// Allocation still has initialized bytes.
640    NotFullyUninitialized,
641    /// Byte range error.
642    ByteRange(ByteRangeError),
643    /// Too many allocations.
644    TooManyAllocations,
645}
646
647impl From<ByteRangeError> for VHeapError {
648    fn from(e: ByteRangeError) -> Self {
649        VHeapError::ByteRange(e)
650    }
651}
652
653// ============================================================================
654// Operation history tracking (for debugging, not for Kani)
655// ============================================================================
656
657/// An operation that was performed on an allocation.
658#[cfg(all(not(kani), not(creusot)))]
659#[derive(Clone)]
660pub struct HeapOp {
661    /// What kind of operation
662    pub kind: HeapOpKind,
663    /// Byte range affected (if applicable)
664    pub range: Option<Range>,
665    /// Backtrace at the time of the operation (stored as string since Backtrace isn't Clone)
666    pub backtrace: String,
667}
668
669#[cfg(all(not(kani), not(creusot)))]
670impl std::fmt::Debug for HeapOp {
671    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
672        write!(f, "{:?}", self.kind)?;
673        if let Some(range) = self.range {
674            write!(f, " [{}..{}]", range.start, range.end)?;
675        }
676        write!(f, "\n{}", self.backtrace)
677    }
678}
679
680/// The kind of heap operation.
681#[derive(Debug, Clone, Copy)]
682pub enum HeapOpKind {
683    Alloc,
684    MarkInit,
685    MarkUninit,
686    DropInPlace,
687    Dealloc,
688    Memcpy,
689    DefaultInPlace,
690}
691
692/// History of operations for one allocation.
693#[cfg(all(not(kani), not(creusot)))]
694#[derive(Clone, Default, Debug)]
695pub struct AllocHistory {
696    pub ops: Vec<HeapOp>,
697}
698
699#[cfg(all(not(kani), not(creusot)))]
700impl AllocHistory {
701    const fn new() -> Self {
702        Self { ops: Vec::new() }
703    }
704
705    fn record(&mut self, kind: HeapOpKind, range: Option<Range>) {
706        let bt = std::backtrace::Backtrace::capture();
707        self.ops.push(HeapOp {
708            kind,
709            range,
710            backtrace: bt.to_string(),
711        });
712    }
713
714    fn print(&self, alloc_id: u8) {
715        eprintln!("=== History for allocation {} ===", alloc_id);
716        for (i, op) in self.ops.iter().enumerate() {
717            eprintln!("--- Op {} ---", i);
718            eprintln!("{:?}", op);
719        }
720        eprintln!("=== End history ===");
721    }
722}
723
724/// A range in the allocation with its init status and shape info.
725#[cfg(all(not(kani), not(creusot)))]
726struct LayoutRange {
727    start: u32,
728    end: u32,
729    depth: usize,
730    shape_kind: &'static str,
731    is_init: bool,
732}
733
734/// Print the allocation layout like a "layer cake" or flame graph.
735#[cfg(all(not(kani), not(creusot)))]
736fn print_allocation_layout<S: IShape>(tracker: &ByteRangeTracker, shape: S, alloc_size: u32) {
737    let mut ranges: Vec<LayoutRange> = Vec::new();
738
739    // Recursively collect all ranges from the shape hierarchy
740    fn collect_ranges<S: IShape>(
741        ranges: &mut Vec<LayoutRange>,
742        tracker: &ByteRangeTracker,
743        shape: S,
744        base_offset: u32,
745        depth: usize,
746    ) {
747        let layout = shape.layout().expect("IShape requires sized types");
748        let size = layout.size() as u32;
749        if size == 0 {
750            return;
751        }
752
753        let start = base_offset;
754        let end = base_offset + size;
755        let is_init = tracker.is_init(start, end);
756
757        let shape_kind = if shape.is_struct() {
758            "struct"
759        } else {
760            "scalar"
761        };
762
763        ranges.push(LayoutRange {
764            start,
765            end,
766            depth,
767            shape_kind,
768            is_init,
769        });
770
771        // If struct, recurse into fields
772        if let Some(st) = shape.as_struct() {
773            for i in 0..st.field_count() {
774                if let Some(field) = st.field(i) {
775                    let field_shape = field.shape();
776                    let field_offset = base_offset + field.offset() as u32;
777                    collect_ranges(ranges, tracker, field_shape, field_offset, depth + 1);
778                }
779            }
780        }
781    }
782
783    collect_ranges(&mut ranges, tracker, shape, 0, 0);
784
785    // Sort by depth (deepest first for printing), then by start
786    ranges.sort_by(|a, b| b.depth.cmp(&a.depth).then(a.start.cmp(&b.start)));
787
788    eprintln!("=== Allocation Layout (size={}) ===", alloc_size);
789    eprintln!("Legend: [####] = initialized, [....] = uninitialized");
790    eprintln!();
791
792    // Find max depth for indentation
793    let _max_depth = ranges.iter().map(|r| r.depth).max().unwrap_or(0);
794
795    // Print from shallowest to deepest (reverse the sort for display)
796    ranges.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.start.cmp(&b.start)));
797
798    for range in &ranges {
799        let indent = "  ".repeat(range.depth);
800        let width = (range.end - range.start) as usize;
801        let bar = if range.is_init {
802            "#".repeat(width.min(40))
803        } else {
804            ".".repeat(width.min(40))
805        };
806        let status = if range.is_init { "INIT" } else { "UNINIT" };
807        eprintln!(
808            "{}[{:3}..{:3}] {} {} [{}]",
809            indent, range.start, range.end, range.shape_kind, status, bar
810        );
811    }
812
813    // Also show the raw init ranges from the tracker
814    eprintln!();
815    eprintln!("Raw initialized ranges:");
816    let init_ranges = tracker.ranges();
817    if init_ranges.is_empty() {
818        eprintln!("  (none)");
819    } else {
820        for range in init_ranges {
821            eprintln!("  [{}..{})", range.start, range.end);
822        }
823    }
824    eprintln!("=== End Layout ===");
825}
826
827/// Verified heap that tracks state for Kani proofs.
828///
829/// This heap doesn't do any real memory operations - it just tracks
830/// the abstract state of allocations and asserts that all transitions are valid.
831#[derive(Debug)]
832pub struct VHeap<S: IShape> {
833    /// Each allocation's initialization state and shape.
834    /// None = freed, Some((tracker, shape)) = live allocation.
835    allocs: [Option<(ByteRangeTracker, S)>; MAX_VHEAP_ALLOCS],
836    /// Next allocation ID to assign.
837    next_id: u8,
838    /// Operation history for each allocation (only when not running under Kani).
839    #[cfg(all(not(kani), not(creusot)))]
840    history: [AllocHistory; MAX_VHEAP_ALLOCS],
841}
842
843impl<S: IShape> VHeap<S> {
844    /// Create a new verified heap with no allocations.
845    pub fn new() -> Self {
846        Self {
847            allocs: [const { None }; MAX_VHEAP_ALLOCS],
848            next_id: 0,
849            #[cfg(all(not(kani), not(creusot)))]
850            history: [const { AllocHistory::new() }; MAX_VHEAP_ALLOCS],
851        }
852    }
853
854    /// Record an operation in the history (no-op under Kani).
855    #[cfg(all(not(kani), not(creusot)))]
856    fn record_op(&mut self, alloc_id: u8, kind: HeapOpKind, range: Option<Range>) {
857        self.history[alloc_id as usize].record(kind, range);
858    }
859
860    #[cfg(any(kani, creusot))]
861    fn record_op(&mut self, _alloc_id: u8, _kind: HeapOpKind, _range: Option<Range>) {}
862
863    /// Print the history for an allocation (no-op under Kani).
864    #[cfg(all(not(kani), not(creusot)))]
865    fn print_history(&self, alloc_id: u8) {
866        self.history[alloc_id as usize].print(alloc_id);
867        // Also print the current layout if allocation is still live
868        if let Some((tracker, shape)) = &self.allocs[alloc_id as usize] {
869            let alloc_size = shape.layout().map(|l| l.size() as u32).unwrap_or(0);
870            print_allocation_layout(tracker, *shape, alloc_size);
871        }
872    }
873
874    #[cfg(any(kani, creusot))]
875    fn print_history(&self, _alloc_id: u8) {}
876
877    /// Assert a condition, printing history and panicking with message if false.
878    fn assert_with_history(&self, cond: bool, alloc_id: u8, msg: &str) {
879        if !cond {
880            self.print_history(alloc_id);
881            panic!("{}", msg);
882        }
883    }
884
885    /// Check that all allocations have been freed (for leak detection).
886    #[cfg(test)]
887    pub fn assert_no_leaks(&self) {
888        for (i, alloc) in self.allocs.iter().enumerate() {
889            assert!(alloc.is_none(), "allocation {} not freed", i);
890        }
891    }
892
893    /// Get the tracker for an allocation, panicking if freed.
894    fn get_tracker(&self, alloc_id: u8) -> &(ByteRangeTracker, S) {
895        self.allocs[alloc_id as usize]
896            .as_ref()
897            .expect("allocation already freed")
898    }
899
900    /// Get the tracker mutably for an allocation, panicking if freed.
901    fn get_tracker_mut(&mut self, alloc_id: u8) -> &mut (ByteRangeTracker, S) {
902        self.allocs[alloc_id as usize]
903            .as_mut()
904            .expect("allocation already freed")
905    }
906
907    #[cfg(not(creusot))]
908    fn matches_subshape(stored: S, offset: usize, target: S) -> bool {
909        if offset == 0 && stored == target {
910            return true;
911        }
912
913        if !stored.is_struct() {
914            return false;
915        }
916
917        let st = stored.as_struct().expect("struct shape");
918        let field_count = st.field_count();
919        for i in 0..field_count {
920            let field = st.field(i).expect("field index in range");
921            let field_shape = field.shape();
922            let field_size = field_shape
923                .layout()
924                .expect("IShape requires sized types")
925                .size();
926            let start = field.offset();
927            let end = start + field_size;
928            if offset >= start && offset < end {
929                return Self::matches_subshape(field_shape, offset - start, target);
930            }
931        }
932
933        false
934    }
935
936    #[cfg(creusot)]
937    fn matches_subshape(_stored: S, _offset: usize, _target: S) -> bool {
938        true
939    }
940}
941
942impl<S: IShape> Default for VHeap<S> {
943    fn default() -> Self {
944        Self::new()
945    }
946}
947
948impl<S: IShape> IHeap<S> for VHeap<S> {
949    type Ptr = VPtr;
950
951    unsafe fn alloc(&mut self, shape: S) -> VPtr {
952        let id = self.next_id;
953        #[cfg(not(creusot))]
954        assert!(
955            (id as usize) < MAX_VHEAP_ALLOCS,
956            "too many allocations (max {})",
957            MAX_VHEAP_ALLOCS
958        );
959        #[cfg(creusot)]
960        {
961            if (id as usize) >= MAX_VHEAP_ALLOCS {
962                panic!("too many allocations");
963            }
964        }
965
966        let layout = shape.layout().expect("IShape requires sized types");
967        self.allocs[id as usize] = Some((ByteRangeTracker::new(), shape));
968        self.next_id += 1;
969
970        self.record_op(
971            id,
972            HeapOpKind::Alloc,
973            Some(Range::new(0, layout.size() as u32)),
974        );
975
976        VPtr::new(id, layout.size() as u32)
977    }
978
979    unsafe fn dealloc(&mut self, ptr: VPtr, shape: S) {
980        let id = ptr.alloc_id();
981        self.assert_with_history(
982            ptr.is_at_start(),
983            id,
984            "dealloc: pointer not at allocation start",
985        );
986
987        let (tracker, stored_shape) = self.get_tracker(id);
988        #[cfg(not(creusot))]
989        self.assert_with_history(*stored_shape == shape, id, "dealloc: shape mismatch");
990        #[cfg(creusot)]
991        {
992            if *stored_shape != shape {
993                self.print_history(id);
994                panic!("dealloc: shape mismatch");
995            }
996        }
997        self.assert_with_history(
998            tracker.is_empty(),
999            id,
1000            "dealloc: allocation still has initialized bytes",
1001        );
1002
1003        self.record_op(id, HeapOpKind::Dealloc, None);
1004        self.allocs[id as usize] = None;
1005    }
1006
1007    unsafe fn memcpy(&mut self, dst: VPtr, src: VPtr, len: usize) {
1008        if len == 0 {
1009            return;
1010        }
1011
1012        // Bounds checks
1013        if dst.offset_bytes() + len > dst.alloc_size() {
1014            self.print_history(dst.alloc_id());
1015            panic!("memcpy: dst out of bounds");
1016        }
1017        if src.offset_bytes() + len > src.alloc_size() {
1018            self.print_history(src.alloc_id());
1019            panic!("memcpy: src out of bounds");
1020        }
1021
1022        // Check src is initialized
1023        let (src_tracker, _) = self.get_tracker(src.alloc_id());
1024        if !src_tracker.is_init(src.offset, src.offset + len as u32) {
1025            self.print_history(src.alloc_id());
1026            panic!("memcpy: src range not initialized");
1027        }
1028
1029        // Record the memcpy on dst
1030        self.record_op(
1031            dst.alloc_id(),
1032            HeapOpKind::Memcpy,
1033            Some(Range::new(dst.offset, dst.offset + len as u32)),
1034        );
1035
1036        // Check dst is uninitialized and mark it initialized
1037        let (dst_tracker, _) = self.get_tracker_mut(dst.alloc_id());
1038        if let Err(e) = dst_tracker.mark_init(dst.offset, dst.offset + len as u32) {
1039            self.print_history(dst.alloc_id());
1040            panic!(
1041                "memcpy: dst range already initialized (forgot to drop?): {:?}",
1042                e
1043            );
1044        }
1045    }
1046
1047    unsafe fn drop_in_place(&mut self, ptr: VPtr, shape: S) {
1048        let layout = shape.layout().expect("IShape requires sized types");
1049        if layout.size() == 0 {
1050            return; // ZST - nothing to drop
1051        }
1052
1053        let alloc_id = ptr.alloc_id();
1054        self.record_op(
1055            alloc_id,
1056            HeapOpKind::DropInPlace,
1057            Some(Range::new(ptr.offset, ptr.offset + layout.size() as u32)),
1058        );
1059
1060        let (tracker, stored_shape) = self.get_tracker_mut(alloc_id);
1061
1062        if !Self::matches_subshape(*stored_shape, ptr.offset_bytes(), shape) {
1063            self.print_history(alloc_id);
1064            panic!("drop_in_place: shape mismatch");
1065        }
1066
1067        if ptr.offset_bytes() + layout.size() > ptr.alloc_size() {
1068            self.print_history(alloc_id);
1069            panic!("drop_in_place: out of bounds");
1070        }
1071
1072        let base = ptr.offset;
1073        let end = base + layout.size() as u32;
1074
1075        if shape.is_struct() {
1076            let st = shape.as_struct().expect("struct shape");
1077            let field_count = st.field_count();
1078
1079            for i in 0..field_count {
1080                let field = st.field(i).expect("field index in range");
1081                let field_shape = field.shape();
1082                let field_size = field_shape
1083                    .layout()
1084                    .expect("IShape requires sized types")
1085                    .size() as u32;
1086                if field_size == 0 {
1087                    continue;
1088                }
1089                let start = base + field.offset() as u32;
1090                let field_end = start + field_size;
1091                if field_end > end {
1092                    self.print_history(alloc_id);
1093                    panic!("drop_in_place: field out of bounds");
1094                }
1095
1096                if let Err(e) = tracker.mark_uninit(start, field_end) {
1097                    self.print_history(alloc_id);
1098                    panic!("drop_in_place: field range not initialized: {:?}", e);
1099                }
1100            }
1101
1102            if let Err(e) = tracker.clear_range(base, end) {
1103                self.print_history(alloc_id);
1104                panic!("drop_in_place: clear_range failed: {:?}", e);
1105            }
1106            return;
1107        }
1108
1109        if let Err(e) = tracker.mark_uninit(base, end) {
1110            self.print_history(alloc_id);
1111            panic!("drop_in_place: range not initialized: {:?}", e);
1112        }
1113    }
1114
1115    unsafe fn default_in_place(&mut self, ptr: VPtr, shape: S) -> bool {
1116        let layout = match shape.layout() {
1117            Some(layout) => layout,
1118            None => return false,
1119        };
1120        let len = layout.size();
1121        if len == 0 {
1122            return true;
1123        }
1124
1125        let alloc_id = ptr.alloc_id();
1126
1127        // Bounds check
1128        if ptr.offset_bytes() + len > ptr.alloc_size() {
1129            self.print_history(alloc_id);
1130            panic!("default_in_place: out of bounds");
1131        }
1132
1133        self.record_op(
1134            alloc_id,
1135            HeapOpKind::DefaultInPlace,
1136            Some(Range::new(ptr.offset, ptr.offset + len as u32)),
1137        );
1138
1139        let (tracker, _) = self.get_tracker_mut(alloc_id);
1140        if let Err(e) = tracker.mark_init(ptr.offset, ptr.offset + len as u32) {
1141            self.print_history(alloc_id);
1142            panic!("default_in_place: range already initialized: {:?}", e);
1143        }
1144        true
1145    }
1146}
1147
1148// ==================================================================
1149// Arena
1150// ==================================================================
1151
1152/// Maximum number of arena slots for verification.
1153pub const MAX_VARENA_SLOTS: usize = 16;
1154
1155/// Slot state for verified arena.
1156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1157enum VSlotState {
1158    Empty,
1159    Occupied,
1160}
1161
1162/// Fixed-size arena for Kani verification.
1163///
1164/// Uses a fixed-size array instead of Vec to keep state space bounded.
1165pub struct VArena<T, const N: usize = MAX_VARENA_SLOTS> {
1166    /// Slot storage. Index 0 is reserved (NOT_STARTED sentinel).
1167    slots: [Option<T>; N],
1168    /// State of each slot.
1169    states: [VSlotState; N],
1170    /// Next slot to try allocating from.
1171    next: usize,
1172}
1173
1174impl<T, const N: usize> VArena<T, N> {
1175    /// Create a new verified arena.
1176    pub fn new() -> Self {
1177        Self {
1178            slots: core::array::from_fn(|_| None),
1179            states: [VSlotState::Empty; N],
1180            next: 1, // Skip slot 0 (reserved for NOT_STARTED)
1181        }
1182    }
1183}
1184
1185impl<T, const N: usize> Default for VArena<T, N> {
1186    fn default() -> Self {
1187        Self::new()
1188    }
1189}
1190
1191impl<T, const N: usize> IArena<T> for VArena<T, N> {
1192    fn alloc(&mut self, value: T) -> Idx<T> {
1193        // Find an empty slot starting from next
1194        for i in self.next..N {
1195            if self.states[i] == VSlotState::Empty {
1196                self.slots[i] = Some(value);
1197                self.states[i] = VSlotState::Occupied;
1198                self.next = i + 1;
1199                return Idx::from_raw(i as u32);
1200            }
1201        }
1202        // Wrap around and search from beginning (skip slot 0)
1203        for i in 1..self.next {
1204            if self.states[i] == VSlotState::Empty {
1205                self.slots[i] = Some(value);
1206                self.states[i] = VSlotState::Occupied;
1207                self.next = i + 1;
1208                return Idx::from_raw(i as u32);
1209            }
1210        }
1211        panic!("arena full");
1212    }
1213
1214    fn free(&mut self, id: Idx<T>) -> T {
1215        assert!(id.is_valid(), "cannot free sentinel index");
1216        let idx = id.index();
1217        assert!(idx < N, "index out of bounds");
1218        assert!(
1219            self.states[idx] == VSlotState::Occupied,
1220            "double-free or freeing empty slot"
1221        );
1222
1223        self.states[idx] = VSlotState::Empty;
1224        self.slots[idx].take().expect("slot was occupied but empty")
1225    }
1226
1227    fn get(&self, id: Idx<T>) -> &T {
1228        assert!(id.is_valid(), "cannot get sentinel index");
1229        let idx = id.index();
1230        assert!(idx < N, "index out of bounds");
1231        assert!(
1232            self.states[idx] == VSlotState::Occupied,
1233            "slot is not occupied"
1234        );
1235
1236        self.slots[idx]
1237            .as_ref()
1238            .expect("slot was occupied but empty")
1239    }
1240
1241    fn get_mut(&mut self, id: Idx<T>) -> &mut T {
1242        assert!(id.is_valid(), "cannot get sentinel index");
1243        let idx = id.index();
1244        assert!(idx < N, "index out of bounds");
1245        assert!(
1246            self.states[idx] == VSlotState::Occupied,
1247            "slot is not occupied"
1248        );
1249
1250        self.slots[idx]
1251            .as_mut()
1252            .expect("slot was occupied but empty")
1253    }
1254}
1255
1256// ==================================================================
1257// Tests
1258// ==================================================================
1259
1260#[cfg(test)]
1261mod tests;
1262#[cfg(test)]
1263mod verus_bridge;
1264
1265#[cfg(kani)]
1266mod proofs;
1267#[cfg(creusot)]
1268#[derive(Debug, Clone, Copy, PartialEq, Eq, DeepModel)]
1269pub struct VLayout {
1270    pub size: usize,
1271    pub align: usize,
1272}
1273
1274#[cfg(creusot)]
1275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1276pub struct VLayoutError;
1277
1278#[cfg(creusot)]
1279impl VLayout {
1280    #[inline]
1281    pub const fn new<T>() -> Self {
1282        Self {
1283            size: core::mem::size_of::<T>(),
1284            align: core::mem::align_of::<T>(),
1285        }
1286    }
1287
1288    #[inline]
1289    pub const fn size(self) -> usize {
1290        self.size
1291    }
1292
1293    #[inline]
1294    pub const fn align(self) -> usize {
1295        self.align
1296    }
1297
1298    pub fn from_size_align(size: usize, align: usize) -> Result<Self, VLayoutError> {
1299        if align == 0 || !align.is_power_of_two() {
1300            return Err(VLayoutError);
1301        }
1302        Ok(Self { size, align })
1303    }
1304
1305    #[inline]
1306    pub fn to_layout(self) -> Layout {
1307        Layout::from_size_align(self.size, self.align).expect("valid layout")
1308    }
1309}
1310
1311#[cfg(not(creusot))]
1312pub type VLayout = Layout;
1313
1314#[cfg(not(creusot))]
1315pub type VLayoutError = std::alloc::LayoutError;