## Coverage Report

#### Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/ruchy/src/runtime/safe_arena.rs

    Line
    Count
    Source
    1
    //! Safe arena allocator without unsafe code
    2
    //!
    3
    //! Provides bounded memory allocation using safe Rust abstractions.
    4
    5
    use anyhow::{Result, anyhow};
    6
    use std::cell::RefCell;
    7
    use std::rc::Rc;
    8
    9
    // ============================================================================
    10
    // Safe Arena Allocator
    11
    // ============================================================================
    12
    13
    /// Safe arena allocator using Rc for memory management
    14
    #[derive(Debug)]
    15
    pub struct SafeArena {
    16
        /// Storage for allocated values
    17
        storage: RefCell<Vec<Box<dyn std::any::Any>>>,
    18
        /// Current memory usage estimate
    19
        used: RefCell<usize>,
    20
        /// Maximum allowed memory
    21
        max_size: usize,
    22
    }
    23
    24
    impl SafeArena {
    25
        /// Create a new arena with the given size limit
    26
    0
        pub fn new(max_size: usize) -> Self {
    27
    0
            Self {
    28
    0
                storage: RefCell::new(Vec::new()),
    29
    0
                used: RefCell::new(0),
    30
    0
                max_size,
    31
    0
            }
    32
    0
        }
    33
    34
        /// Allocate a value in the arena
    35
    0
        pub fn alloc<T: 'static>(&self, value: T) -> Result<ArenaRef<'_, T>> {
    36
    0
            let size = std::mem::size_of::<T>();
    37
    38
            // Check memory limit
    39
    0
            if *self.used.borrow() + size > self.max_size {
    40
    0
                return Err(anyhow!("Arena memory limit exceeded"));
    41
    0
            }
    42
    43
            // Store value in Rc
    44
    0
            let rc_value = Rc::new(value);
    45
    0
            self.storage.borrow_mut().push(Box::new(rc_value.clone()) as Box<dyn std::any::Any>);
    46
    0
            *self.used.borrow_mut() += size;
    47
    48
    0
            Ok(ArenaRef {
    49
    0
                value: rc_value,
    50
    0
                _arena: self,
    51
    0
            })
    52
    0
        }
    53
    54
        /// Reset the arena, clearing all allocations
    55
    0
        pub fn reset(&self) {
    56
    0
            self.storage.borrow_mut().clear();
    57
    0
            *self.used.borrow_mut() = 0;
    58
    0
        }
    59
    60
        /// Get current memory usage
    61
    0
        pub fn used(&self) -> usize {
    62
    0
            *self.used.borrow()
    63
    0
        }
    64
    }
    65
    66
    /// Reference to a value in the arena
    67
    #[derive(Debug)]
    68
    pub struct ArenaRef<'a, T> {
    69
        value: Rc<T>,
    70
        _arena: &'a SafeArena,
    71
    }
    72
    73
    impl<T> std::ops::Deref for ArenaRef<'_, T> {
    74
        type Target = T;
    75
    76
    0
        fn deref(&self) -> &Self::Target {
    77
    0
            &self.value
    78
    0
        }
    79
    }
    80
    81
    // ============================================================================
    82
    // Transactional Arena
    83
    // ============================================================================
    84
    85
    /// Arena with checkpoint/rollback support
    86
    #[derive(Debug)]
    87
    pub struct TransactionalArena {
    88
        /// Current values
    89
        current: Rc<SafeArena>,
    90
        /// Checkpoints
    91
        checkpoints: Vec<ArenaCheckpoint>,
    92
    }
    93
    94
    #[derive(Clone, Debug)]
    95
    struct ArenaCheckpoint {
    96
        storage_size: usize,
    97
        used: usize,
    98
    }
    99
    100
    impl TransactionalArena {
    101
    0
        pub fn new(max_size: usize) -> Self {
    102
    0
            Self {
    103
    0
                current: Rc::new(SafeArena::new(max_size)),
    104
    0
                checkpoints: Vec::new(),
    105
    0
            }
    106
    0
        }
    107
    108
    0
        pub fn checkpoint(&mut self) -> usize {
    109
    0
            let checkpoint = ArenaCheckpoint {
    110
    0
                storage_size: self.current.storage.borrow().len(),
    111
    0
                used: self.current.used(),
    112
    0
            };
    113
    0
            self.checkpoints.push(checkpoint);
    114
    0
            self.checkpoints.len() - 1
    115
    0
        }
    116
    117
    0
        pub fn rollback(&mut self, checkpoint_id: usize) -> Result<()> {
    118
    0
            if checkpoint_id >= self.checkpoints.len() {
    119
    0
                return Err(anyhow!("Invalid checkpoint"));
    120
    0
            }
    121
    122
    0
            let checkpoint = &self.checkpoints[checkpoint_id];
    123
    124
            // Truncate storage to checkpoint size
    125
    0
            self.current.storage.borrow_mut().truncate(checkpoint.storage_size);
    126
    0
            *self.current.used.borrow_mut() = checkpoint.used;
    127
    128
            // Remove later checkpoints
    129
    0
            self.checkpoints.truncate(checkpoint_id + 1);
    130
    131
    0
            Ok(())
    132
    0
        }
    133
    134
    0
        pub fn commit(&mut self) -> Result<()> {
    135
    0
            if self.checkpoints.is_empty() {
    136
    0
                return Err(anyhow!("No checkpoint to commit"));
    137
    0
            }
    138
    0
            self.checkpoints.pop();
    139
    0
            Ok(())
    140
    0
        }
    141
    142
    0
        pub fn arena(&self) -> &SafeArena {
    143
    0
            &self.current
    144
    0
        }
    145
    146
    0
        pub fn reset(&mut self) {
    147
    0
            self.current.reset();
    148
    0
            self.checkpoints.clear();
    149
    0
        }
    150
    }
    151
    152
    #[cfg(test)]
    153
    mod tests {
    154
        use super::*;
    155
    156
        #[test]
    157
        fn test_safe_arena() {
    158
            let arena = SafeArena::new(1024);
    159
    160
            let v1 = arena.alloc(42).unwrap();
    161
            let v2 = arena.alloc("hello".to_string()).unwrap();
    162
    163
            assert_eq!(*v1, 42);
    164
            assert_eq!(*v2, "hello");
    165
    166
            arena.reset();
    167
            assert_eq!(arena.used(), 0);
    168
        }
    169
    170
        #[test]
    171
        fn test_transactional() {
    172
            let mut arena = TransactionalArena::new(1024);
    173
    174
            arena.arena().alloc(1).unwrap();
    175
            let checkpoint = arena.checkpoint();
    176
    177
            arena.arena().alloc(2).unwrap();
    178
            let used_before = arena.arena().used();
    179
    180
            arena.rollback(checkpoint).unwrap();
    181
            let used_after = arena.arena().used();
    182
    183
            assert!(used_after < used_before);
    184
        }
    185
    186
        #[test]
    187
        fn test_arena_memory_limit() {
    188
            let arena = SafeArena::new(16); // Very small limit
    189
    190
            // First allocation should succeed
    191
            let _val1 = arena.alloc([0u8; 8]).unwrap();
    192
    193
            // Second allocation should fail due to memory limit
    194
            let result = arena.alloc([0u8; 16]);
    195
            assert!(result.is_err());
    196
            assert!(result.unwrap_err().to_string().contains("memory limit exceeded"));
    197
        }
    198
    199
        #[test]
    200
        fn test_arena_used_tracking() {
    201
            let arena = SafeArena::new(1024);
    202
            assert_eq!(arena.used(), 0);
    203
    204
            let _val1 = arena.alloc(42i32).unwrap();
    205
            let used_after_int = arena.used();
    206
            assert!(used_after_int >= 4); // At least size of i32
    207
    208
            let _val2 = arena.alloc("test".to_string()).unwrap();
    209
            let used_after_string = arena.used();
    210
            assert!(used_after_string > used_after_int);
    211
        }
    212
    213
        #[test]
    214
        fn test_arena_ref_deref() {
    215
            let arena = SafeArena::new(1024);
    216
            let val = arena.alloc(vec![1, 2, 3, 4]).unwrap();
    217
    218
            // Test Deref trait
    219
            assert_eq!(val.len(), 4);
    220
            assert_eq!(val[0], 1);
    221
            assert_eq!(val[3], 4);
    222
        }
    223
    224
        #[test]
    225
        fn test_transactional_arena_new() {
    226
            let arena = TransactionalArena::new(2048);
    227
            assert_eq!(arena.arena().used(), 0);
    228
            assert!(arena.checkpoints.is_empty());
    229
        }
    230
    231
        #[test]
    232
        fn test_transactional_arena_multiple_checkpoints() {
    233
            let mut arena = TransactionalArena::new(1024);
    234
    235
            // Initial allocation
    236
            arena.arena().alloc(100).unwrap();
    237
    238
            // First checkpoint
    239
            let cp1 = arena.checkpoint();
    240
            arena.arena().alloc(200).unwrap();
    241
    242
            // Second checkpoint
    243
            let _cp2 = arena.checkpoint();
    244
            arena.arena().alloc(300).unwrap();
    245
    246
            // Rollback to first checkpoint
    247
            arena.rollback(cp1).unwrap();
    248
    249
            // Should only have allocations up to first checkpoint
    250
            let used = arena.arena().used();
    251
            assert!(used >= 4); // At least the first allocation
    252
        }
    253
    254
        #[test]
    255
        fn test_transactional_arena_invalid_checkpoint() {
    256
            let mut arena = TransactionalArena::new(1024);
    257
    258
            // Try to rollback to invalid checkpoint
    259
            let result = arena.rollback(999);
    260
            assert!(result.is_err());
    261
            assert!(result.unwrap_err().to_string().contains("Invalid checkpoint"));
    262
        }
    263
    264
        #[test]
    265
        fn test_transactional_arena_commit() {
    266
            let mut arena = TransactionalArena::new(1024);
    267
    268
            // Create checkpoint
    269
            let _cp = arena.checkpoint();
    270
            assert!(!arena.checkpoints.is_empty());
    271
    272
            // Commit should remove the checkpoint
    273
            arena.commit().unwrap();
    274
            assert!(arena.checkpoints.is_empty());
    275
        }
    276
    277
        #[test]
    278
        fn test_transactional_arena_commit_without_checkpoint() {
    279
            let mut arena = TransactionalArena::new(1024);
    280
    281
            // Try to commit without checkpoint
    282
            let result = arena.commit();
    283
            assert!(result.is_err());
    284
            assert!(result.unwrap_err().to_string().contains("No checkpoint to commit"));
    285
        }
    286
    287
        #[test]
    288
        fn test_transactional_arena_reset() {
    289
            let mut arena = TransactionalArena::new(1024);
    290
    291
            // Add some data and checkpoints
    292
            arena.arena().alloc(42).unwrap();
    293
            arena.checkpoint();
    294
            arena.arena().alloc(84).unwrap();
    295
    296
            assert!(arena.arena().used() > 0);
    297
            assert!(!arena.checkpoints.is_empty());
    298
    299
            // Reset should clear everything
    300
            arena.reset();
    301
            assert_eq!(arena.arena().used(), 0);
    302
            assert!(arena.checkpoints.is_empty());
    303
        }
    304
    305
        #[test]
    306
        fn test_checkpoint_clone() {
    307
            let checkpoint1 = ArenaCheckpoint {
    308
                storage_size: 10,
    309
                used: 100,
    310
            };
    311
            let checkpoint2 = checkpoint1.clone();
    312
    313
            assert_eq!(checkpoint1.storage_size, checkpoint2.storage_size);
    314
            assert_eq!(checkpoint1.used, checkpoint2.used);
    315
        }
    316
    317
        #[test]
    318
        fn test_arena_with_different_types() {
    319
            let arena = SafeArena::new(1024);
    320
    321
            // Allocate different types
    322
            let int_val = arena.alloc(42i32).unwrap();
    323
            let string_val = arena.alloc("hello".to_string()).unwrap();
    324
            let vec_val = arena.alloc(vec![1, 2, 3]).unwrap();
    325
            let bool_val = arena.alloc(true).unwrap();
    326
    327
            // Verify all values
    328
            assert_eq!(*int_val, 42);
    329
            assert_eq!(*string_val, "hello");
    330
            assert_eq!(*vec_val, vec![1, 2, 3]);
    331
            assert!(*bool_val);
    332
        }
    333
    334
        #[test]
    335
        fn test_transactional_arena_nested_operations() {
    336
            let mut arena = TransactionalArena::new(1024);
    337
    338
            // Initial state
    339
            arena.arena().alloc(1).unwrap();
    340
            let initial_used = arena.arena().used();
    341
    342
            // Start transaction
    343
            let cp = arena.checkpoint();
    344
            arena.arena().alloc(2).unwrap();
    345
            arena.arena().alloc(3).unwrap();
    346
    347
            let mid_used = arena.arena().used();
    348
            assert!(mid_used > initial_used);
    349
    350
            // Rollback
    351
            arena.rollback(cp).unwrap();
    352
            let final_used = arena.arena().used();
    353
    354
            assert_eq!(final_used, initial_used);
    355
        }
    356
    357
        #[test]
    358
        fn test_arena_large_allocation() {
    359
            let arena = SafeArena::new(1024);
    360
    361
            // Try to allocate something larger than limit
    362
            let result = arena.alloc([0u8; 2048]);
    363
            assert!(result.is_err());
    364
        }
    365
    366
        #[test]
    367
        fn test_transactional_checkpoint_return_value() {
    368
            let mut arena = TransactionalArena::new(1024);
    369
    370
            let cp1 = arena.checkpoint();
    371
            assert_eq!(cp1, 0);
    372
    373
            let cp2 = arena.checkpoint();
    374
            assert_eq!(cp2, 1);
    375
    376
            let cp3 = arena.checkpoint();
    377
            assert_eq!(cp3, 2);
    378
        }
    379
    }