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
}