Skip to main content

memscope_rs/capture/types/
stack.rs

1//! Stack allocation tracking types.
2//!
3//! This module contains types for tracking stack allocations,
4//! including stack scope information and frame details.
5
6use serde::{Deserialize, Serialize};
7
8/// Stack allocation tracking information.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct StackAllocationInfo {
11    /// Stack frame identifier.
12    pub frame_id: usize,
13    /// Variable name on stack.
14    pub var_name: String,
15    /// Stack offset from frame pointer.
16    pub stack_offset: isize,
17    /// Size of stack allocation.
18    pub size: usize,
19    /// Function name where allocated.
20    pub function_name: String,
21    /// Stack depth level.
22    pub stack_depth: usize,
23    /// Lifetime scope information.
24    pub scope_info: StackScopeInfo,
25}
26
27/// Stack scope information.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct StackScopeInfo {
30    /// Scope type (function, block, loop, etc.).
31    pub scope_type: ScopeType,
32    /// Scope start line number.
33    pub start_line: Option<u32>,
34    /// Scope end line number.
35    pub end_line: Option<u32>,
36    /// Parent scope identifier.
37    pub parent_scope: Option<usize>,
38    /// Nested scope level.
39    pub nesting_level: usize,
40}
41
42/// Scope type enumeration.
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub enum ScopeType {
45    /// Function scope.
46    Function,
47    /// Block scope.
48    Block,
49    /// Loop scope.
50    Loop,
51    /// Conditional scope.
52    Conditional,
53    /// Match scope.
54    Match,
55    /// Async scope.
56    Async,
57    /// Unsafe scope.
58    Unsafe,
59}
60
61// Implement From trait for converting from core::types to capture::types
62impl From<crate::core::types::StackAllocationInfo> for StackAllocationInfo {
63    fn from(old: crate::core::types::StackAllocationInfo) -> Self {
64        Self {
65            frame_id: old.frame_id,
66            var_name: old.var_name,
67            stack_offset: old.stack_offset,
68            size: old.size,
69            function_name: old.function_name,
70            stack_depth: old.stack_depth,
71            scope_info: StackScopeInfo {
72                scope_type: match old.scope_info.scope_type {
73                    crate::core::types::ScopeType::Function => ScopeType::Function,
74                    crate::core::types::ScopeType::Block => ScopeType::Block,
75                    crate::core::types::ScopeType::Loop => ScopeType::Loop,
76                    crate::core::types::ScopeType::Conditional => ScopeType::Conditional,
77                    crate::core::types::ScopeType::Match => ScopeType::Match,
78                    crate::core::types::ScopeType::Async => ScopeType::Async,
79                    crate::core::types::ScopeType::Unsafe => ScopeType::Unsafe,
80                },
81                start_line: old.scope_info.start_line,
82                end_line: old.scope_info.end_line,
83                parent_scope: old.scope_info.parent_scope,
84                nesting_level: old.scope_info.nesting_level,
85            },
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_stack_allocation_info() {
96        let info = StackAllocationInfo {
97            frame_id: 1,
98            var_name: "local_var".to_string(),
99            stack_offset: -16,
100            size: 8,
101            function_name: "main".to_string(),
102            stack_depth: 2,
103            scope_info: StackScopeInfo {
104                scope_type: ScopeType::Function,
105                start_line: Some(10),
106                end_line: Some(20),
107                parent_scope: None,
108                nesting_level: 0,
109            },
110        };
111
112        assert_eq!(info.frame_id, 1);
113        assert_eq!(info.var_name, "local_var");
114        assert_eq!(info.stack_offset, -16);
115    }
116
117    #[test]
118    fn test_scope_type_variants() {
119        let types = vec![
120            ScopeType::Function,
121            ScopeType::Block,
122            ScopeType::Loop,
123            ScopeType::Conditional,
124            ScopeType::Match,
125            ScopeType::Async,
126            ScopeType::Unsafe,
127        ];
128
129        for scope_type in types {
130            assert!(!format!("{scope_type:?}").is_empty());
131        }
132    }
133
134    #[test]
135    fn test_stack_scope_info_creation() {
136        let scope = StackScopeInfo {
137            scope_type: ScopeType::Loop,
138            start_line: Some(5),
139            end_line: Some(15),
140            parent_scope: Some(1),
141            nesting_level: 2,
142        };
143
144        assert_eq!(scope.scope_type, ScopeType::Loop);
145        assert_eq!(scope.start_line, Some(5));
146        assert_eq!(scope.parent_scope, Some(1));
147        assert_eq!(scope.nesting_level, 2);
148    }
149
150    #[test]
151    fn test_stack_allocation_info_with_block_scope() {
152        let info = StackAllocationInfo {
153            frame_id: 2,
154            var_name: "block_var".to_string(),
155            stack_offset: -32,
156            size: 16,
157            function_name: "process".to_string(),
158            stack_depth: 3,
159            scope_info: StackScopeInfo {
160                scope_type: ScopeType::Block,
161                start_line: Some(25),
162                end_line: Some(35),
163                parent_scope: Some(0),
164                nesting_level: 1,
165            },
166        };
167
168        assert_eq!(info.scope_info.scope_type, ScopeType::Block);
169        assert_eq!(info.size, 16);
170    }
171
172    #[test]
173    fn test_stack_allocation_info_with_match_scope() {
174        let info = StackAllocationInfo {
175            frame_id: 3,
176            var_name: "match_var".to_string(),
177            stack_offset: -8,
178            size: 4,
179            function_name: "handle".to_string(),
180            stack_depth: 1,
181            scope_info: StackScopeInfo {
182                scope_type: ScopeType::Match,
183                start_line: Some(100),
184                end_line: Some(120),
185                parent_scope: None,
186                nesting_level: 0,
187            },
188        };
189
190        assert_eq!(info.scope_info.scope_type, ScopeType::Match);
191        assert_eq!(info.stack_depth, 1);
192    }
193
194    #[test]
195    fn test_stack_allocation_info_with_async_scope() {
196        let info = StackAllocationInfo {
197            frame_id: 4,
198            var_name: "async_var".to_string(),
199            stack_offset: -64,
200            size: 32,
201            function_name: "async_fn".to_string(),
202            stack_depth: 5,
203            scope_info: StackScopeInfo {
204                scope_type: ScopeType::Async,
205                start_line: Some(1),
206                end_line: Some(50),
207                parent_scope: None,
208                nesting_level: 0,
209            },
210        };
211
212        assert_eq!(info.scope_info.scope_type, ScopeType::Async);
213        assert_eq!(info.function_name, "async_fn");
214    }
215
216    #[test]
217    fn test_stack_allocation_info_with_unsafe_scope() {
218        let info = StackAllocationInfo {
219            frame_id: 5,
220            var_name: "unsafe_var".to_string(),
221            stack_offset: -128,
222            size: 64,
223            function_name: "unsafe_fn".to_string(),
224            stack_depth: 2,
225            scope_info: StackScopeInfo {
226                scope_type: ScopeType::Unsafe,
227                start_line: Some(10),
228                end_line: Some(20),
229                parent_scope: Some(1),
230                nesting_level: 1,
231            },
232        };
233
234        assert_eq!(info.scope_info.scope_type, ScopeType::Unsafe);
235    }
236
237    #[test]
238    fn test_stack_allocation_info_with_conditional_scope() {
239        let info = StackAllocationInfo {
240            frame_id: 6,
241            var_name: "cond_var".to_string(),
242            stack_offset: -24,
243            size: 8,
244            function_name: "check".to_string(),
245            stack_depth: 2,
246            scope_info: StackScopeInfo {
247                scope_type: ScopeType::Conditional,
248                start_line: Some(30),
249                end_line: Some(40),
250                parent_scope: None,
251                nesting_level: 0,
252            },
253        };
254
255        assert_eq!(info.scope_info.scope_type, ScopeType::Conditional);
256    }
257
258    #[test]
259    fn test_stack_scope_info_no_lines() {
260        let scope = StackScopeInfo {
261            scope_type: ScopeType::Function,
262            start_line: None,
263            end_line: None,
264            parent_scope: None,
265            nesting_level: 0,
266        };
267
268        assert!(scope.start_line.is_none());
269        assert!(scope.end_line.is_none());
270    }
271
272    #[test]
273    fn test_stack_allocation_info_serialization() {
274        let info = StackAllocationInfo {
275            frame_id: 10,
276            var_name: "serialized_var".to_string(),
277            stack_offset: -48,
278            size: 24,
279            function_name: "test_serialization".to_string(),
280            stack_depth: 4,
281            scope_info: StackScopeInfo {
282                scope_type: ScopeType::Function,
283                start_line: Some(1),
284                end_line: Some(10),
285                parent_scope: None,
286                nesting_level: 0,
287            },
288        };
289
290        let json = serde_json::to_string(&info).unwrap();
291        let deserialized: StackAllocationInfo = serde_json::from_str(&json).unwrap();
292        assert_eq!(deserialized.frame_id, info.frame_id);
293        assert_eq!(deserialized.var_name, info.var_name);
294    }
295
296    #[test]
297    fn test_stack_scope_info_serialization() {
298        let scope = StackScopeInfo {
299            scope_type: ScopeType::Loop,
300            start_line: Some(5),
301            end_line: Some(15),
302            parent_scope: Some(1),
303            nesting_level: 2,
304        };
305
306        let json = serde_json::to_string(&scope).unwrap();
307        let deserialized: StackScopeInfo = serde_json::from_str(&json).unwrap();
308        assert_eq!(deserialized.scope_type, scope.scope_type);
309        assert_eq!(deserialized.nesting_level, scope.nesting_level);
310    }
311
312    #[test]
313    fn test_scope_type_serialization() {
314        let types = vec![
315            ScopeType::Function,
316            ScopeType::Block,
317            ScopeType::Loop,
318            ScopeType::Conditional,
319            ScopeType::Match,
320            ScopeType::Async,
321            ScopeType::Unsafe,
322        ];
323
324        for scope_type in types {
325            let json = serde_json::to_string(&scope_type).unwrap();
326            let deserialized: ScopeType = serde_json::from_str(&json).unwrap();
327            assert_eq!(deserialized, scope_type);
328        }
329    }
330
331    #[test]
332    fn test_stack_allocation_info_clone() {
333        let info = StackAllocationInfo {
334            frame_id: 1,
335            var_name: "clone_test".to_string(),
336            stack_offset: -16,
337            size: 8,
338            function_name: "test".to_string(),
339            stack_depth: 1,
340            scope_info: StackScopeInfo {
341                scope_type: ScopeType::Function,
342                start_line: None,
343                end_line: None,
344                parent_scope: None,
345                nesting_level: 0,
346            },
347        };
348
349        let cloned = info.clone();
350        assert_eq!(cloned.frame_id, info.frame_id);
351        assert_eq!(cloned.var_name, info.var_name);
352    }
353
354    #[test]
355    fn test_stack_scope_info_clone() {
356        let scope = StackScopeInfo {
357            scope_type: ScopeType::Block,
358            start_line: Some(10),
359            end_line: Some(20),
360            parent_scope: Some(5),
361            nesting_level: 3,
362        };
363
364        let cloned = scope.clone();
365        assert_eq!(cloned.scope_type, scope.scope_type);
366        assert_eq!(cloned.nesting_level, scope.nesting_level);
367    }
368
369    #[test]
370    fn test_stack_allocation_info_debug() {
371        let info = StackAllocationInfo {
372            frame_id: 1,
373            var_name: "debug_test".to_string(),
374            stack_offset: -16,
375            size: 8,
376            function_name: "test".to_string(),
377            stack_depth: 1,
378            scope_info: StackScopeInfo {
379                scope_type: ScopeType::Function,
380                start_line: None,
381                end_line: None,
382                parent_scope: None,
383                nesting_level: 0,
384            },
385        };
386
387        let debug_str = format!("{:?}", info);
388        assert!(debug_str.contains("StackAllocationInfo"));
389        assert!(debug_str.contains("frame_id"));
390    }
391
392    #[test]
393    fn test_stack_scope_info_debug() {
394        let scope = StackScopeInfo {
395            scope_type: ScopeType::Loop,
396            start_line: Some(1),
397            end_line: Some(10),
398            parent_scope: None,
399            nesting_level: 0,
400        };
401
402        let debug_str = format!("{:?}", scope);
403        assert!(debug_str.contains("StackScopeInfo"));
404        assert!(debug_str.contains("scope_type"));
405    }
406
407    #[test]
408    fn test_stack_allocation_info_equality() {
409        let info1 = StackAllocationInfo {
410            frame_id: 1,
411            var_name: "test".to_string(),
412            stack_offset: -16,
413            size: 8,
414            function_name: "fn".to_string(),
415            stack_depth: 1,
416            scope_info: StackScopeInfo {
417                scope_type: ScopeType::Function,
418                start_line: None,
419                end_line: None,
420                parent_scope: None,
421                nesting_level: 0,
422            },
423        };
424
425        let info2 = StackAllocationInfo {
426            frame_id: 1,
427            var_name: "test".to_string(),
428            stack_offset: -16,
429            size: 8,
430            function_name: "fn".to_string(),
431            stack_depth: 1,
432            scope_info: StackScopeInfo {
433                scope_type: ScopeType::Function,
434                start_line: None,
435                end_line: None,
436                parent_scope: None,
437                nesting_level: 0,
438            },
439        };
440
441        assert_eq!(info1, info2);
442    }
443
444    #[test]
445    fn test_stack_scope_info_equality() {
446        let scope1 = StackScopeInfo {
447            scope_type: ScopeType::Function,
448            start_line: Some(1),
449            end_line: Some(10),
450            parent_scope: None,
451            nesting_level: 0,
452        };
453
454        let scope2 = StackScopeInfo {
455            scope_type: ScopeType::Function,
456            start_line: Some(1),
457            end_line: Some(10),
458            parent_scope: None,
459            nesting_level: 0,
460        };
461
462        assert_eq!(scope1, scope2);
463    }
464
465    #[test]
466    fn test_boundary_values_stack_allocation() {
467        let info = StackAllocationInfo {
468            frame_id: usize::MAX,
469            var_name: String::new(),
470            stack_offset: isize::MIN,
471            size: usize::MAX,
472            function_name: String::new(),
473            stack_depth: usize::MAX,
474            scope_info: StackScopeInfo {
475                scope_type: ScopeType::Function,
476                start_line: Some(u32::MAX),
477                end_line: Some(u32::MAX),
478                parent_scope: Some(usize::MAX),
479                nesting_level: usize::MAX,
480            },
481        };
482
483        assert_eq!(info.frame_id, usize::MAX);
484        assert_eq!(info.stack_offset, isize::MIN);
485        assert_eq!(info.size, usize::MAX);
486    }
487
488    #[test]
489    fn test_negative_stack_offset() {
490        let info = StackAllocationInfo {
491            frame_id: 1,
492            var_name: "negative_offset".to_string(),
493            stack_offset: -1024,
494            size: 64,
495            function_name: "test".to_string(),
496            stack_depth: 1,
497            scope_info: StackScopeInfo {
498                scope_type: ScopeType::Function,
499                start_line: None,
500                end_line: None,
501                parent_scope: None,
502                nesting_level: 0,
503            },
504        };
505
506        assert_eq!(info.stack_offset, -1024);
507    }
508
509    #[test]
510    fn test_positive_stack_offset() {
511        let info = StackAllocationInfo {
512            frame_id: 1,
513            var_name: "positive_offset".to_string(),
514            stack_offset: 256,
515            size: 32,
516            function_name: "test".to_string(),
517            stack_depth: 1,
518            scope_info: StackScopeInfo {
519                scope_type: ScopeType::Function,
520                start_line: None,
521                end_line: None,
522                parent_scope: None,
523                nesting_level: 0,
524            },
525        };
526
527        assert_eq!(info.stack_offset, 256);
528    }
529
530    #[test]
531    fn test_deep_nesting_level() {
532        let scope = StackScopeInfo {
533            scope_type: ScopeType::Block,
534            start_line: Some(1),
535            end_line: Some(10),
536            parent_scope: Some(99),
537            nesting_level: 100,
538        };
539
540        assert_eq!(scope.nesting_level, 100);
541    }
542}