memscope_rs/core/
ownership_history.rs

1//! Ownership history tracking system
2//!
3//! This module provides detailed tracking of ownership events for memory allocations,
4//! including cloning, borrowing, ownership transfers, and lifetime analysis.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::atomic::{AtomicU64, Ordering};
9
10/// Global event ID generator
11static EVENT_ID_GENERATOR: AtomicU64 = AtomicU64::new(1);
12
13/// Ownership history recorder for tracking detailed ownership events
14pub struct OwnershipHistoryRecorder {
15    /// Map from allocation pointer to its ownership events
16    ownership_events: HashMap<usize, Vec<OwnershipEvent>>,
17    /// Map from allocation pointer to its current ownership summary
18    ownership_summaries: HashMap<usize, OwnershipSummary>,
19    /// Configuration for history recording
20    config: HistoryConfig,
21}
22
23/// Configuration for ownership history recording
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct HistoryConfig {
26    /// Maximum number of events to keep per allocation
27    pub max_events_per_allocation: usize,
28    /// Enable detailed borrowing tracking
29    pub track_borrowing: bool,
30    /// Enable clone relationship tracking
31    pub track_cloning: bool,
32    /// Enable ownership transfer tracking
33    pub track_ownership_transfers: bool,
34}
35
36impl Default for HistoryConfig {
37    fn default() -> Self {
38        Self {
39            max_events_per_allocation: 100,
40            track_borrowing: true,
41            track_cloning: true,
42            track_ownership_transfers: true,
43        }
44    }
45}
46
47/// Types of ownership events as defined in improve.md
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49pub enum OwnershipEventType {
50    /// Memory was allocated
51    Allocated,
52    /// Object was cloned from another object
53    Cloned { source_ptr: usize },
54    /// Object was dropped/deallocated
55    Dropped,
56    /// Ownership was transferred to another variable
57    OwnershipTransferred { target_var: String },
58    /// Object was borrowed (immutably)
59    Borrowed { borrower_scope: String },
60    /// Object was mutably borrowed
61    MutablyBorrowed { borrower_scope: String },
62    /// Borrow was released
63    BorrowReleased { borrower_scope: String },
64    /// Reference count changed (for Rc/Arc)
65    RefCountChanged { old_count: usize, new_count: usize },
66}
67
68/// A single ownership event in the history as defined in improve.md
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct OwnershipEvent {
71    /// Unique event ID for tracking
72    pub event_id: u64,
73    /// Timestamp when the event occurred (nanoseconds since epoch)
74    pub timestamp: u64,
75    /// Type of ownership event (Allocated, Cloned, Dropped, etc.)
76    pub event_type: OwnershipEventType,
77    /// ID pointing to the call stack that triggered this event
78    pub source_stack_id: u32,
79    /// Additional details specific to the event type
80    pub details: OwnershipEventDetails,
81}
82
83/// Additional details for ownership events
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct OwnershipEventDetails {
86    /// Optional clone source pointer (for Cloned events)
87    pub clone_source_ptr: Option<usize>,
88    /// Optional target variable name (for OwnershipTransferred events)
89    pub transfer_target_var: Option<String>,
90    /// Optional borrower scope (for borrow events)
91    pub borrower_scope: Option<String>,
92    /// Optional reference count information
93    pub ref_count_info: Option<RefCountInfo>,
94    /// Optional additional context
95    pub context: Option<String>,
96}
97
98/// Reference count information for smart pointers
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct RefCountInfo {
101    pub strong_count: usize,
102    pub weak_count: usize,
103    pub data_ptr: usize,
104}
105
106/// High-level ownership summary for an allocation
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct OwnershipSummary {
109    /// Pointer to the allocation
110    pub allocation_ptr: usize,
111    /// Total lifetime in milliseconds (if known)
112    pub lifetime_ms: Option<u64>,
113    /// Borrowing information
114    pub borrow_info: BorrowInfo,
115    /// Cloning information
116    pub clone_info: CloneInfo,
117    /// Whether detailed ownership history is available
118    pub ownership_history_available: bool,
119    /// Total number of ownership events
120    pub total_events: usize,
121}
122
123/// Detailed borrowing information
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct BorrowInfo {
126    /// Total number of immutable borrows during lifetime
127    pub immutable_borrows: u32,
128    /// Total number of mutable borrows during lifetime
129    pub mutable_borrows: u32,
130    /// Maximum number of concurrent borrows observed
131    pub max_concurrent_borrows: u32,
132    /// Timestamp of the last borrow
133    pub last_borrow_timestamp: Option<u64>,
134    /// Currently active borrows
135    pub active_borrows: Vec<ActiveBorrow>,
136}
137
138/// Information about an active borrow
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ActiveBorrow {
141    pub borrower_scope: String,
142    pub borrow_type: BorrowType,
143    pub start_timestamp: u64,
144}
145
146/// Type of borrow
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub enum BorrowType {
149    Immutable,
150    Mutable,
151}
152
153/// Detailed cloning information
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct CloneInfo {
156    /// Number of times this allocation was cloned
157    pub clone_count: u32,
158    /// Whether this allocation is itself a clone
159    pub is_clone: bool,
160    /// Pointer to the original allocation (if this is a clone)
161    pub original_ptr: Option<usize>,
162    /// List of pointers that were cloned from this allocation
163    pub cloned_ptrs: Vec<usize>,
164}
165
166impl OwnershipHistoryRecorder {
167    /// Create a new ownership history recorder
168    pub fn new() -> Self {
169        Self::with_config(HistoryConfig::default())
170    }
171
172    /// Create a new ownership history recorder with custom configuration
173    pub fn with_config(config: HistoryConfig) -> Self {
174        Self {
175            ownership_events: HashMap::new(),
176            ownership_summaries: HashMap::new(),
177            config,
178        }
179    }
180
181    /// Record a new ownership event
182    pub fn record_event(
183        &mut self,
184        ptr: usize,
185        event_type: OwnershipEventType,
186        source_stack_id: u32,
187    ) {
188        let event_id = EVENT_ID_GENERATOR.fetch_add(1, Ordering::Relaxed);
189        let timestamp = self.get_current_timestamp();
190
191        let details = self.create_event_details(&event_type);
192
193        let event = OwnershipEvent {
194            event_id,
195            timestamp,
196            event_type: event_type.clone(),
197            source_stack_id,
198            details,
199        };
200
201        // Add event to history
202        let events = self.ownership_events.entry(ptr).or_default();
203        events.push(event);
204
205        // Limit the number of events per allocation
206        if events.len() > self.config.max_events_per_allocation {
207            events.remove(0); // Remove oldest event
208        }
209
210        // Update ownership summary
211        self.update_ownership_summary(ptr, &event_type, timestamp);
212    }
213
214    /// Create event details based on event type
215    fn create_event_details(&self, event_type: &OwnershipEventType) -> OwnershipEventDetails {
216        match event_type {
217            OwnershipEventType::Cloned { source_ptr } => OwnershipEventDetails {
218                clone_source_ptr: Some(*source_ptr),
219                transfer_target_var: None,
220                borrower_scope: None,
221                ref_count_info: None,
222                context: Some("Memory cloned from another allocation".to_string()),
223            },
224            OwnershipEventType::OwnershipTransferred { target_var } => OwnershipEventDetails {
225                clone_source_ptr: None,
226                transfer_target_var: Some(target_var.clone()),
227                borrower_scope: None,
228                ref_count_info: None,
229                context: Some("Ownership transferred to another variable".to_string()),
230            },
231            OwnershipEventType::Borrowed { borrower_scope } => OwnershipEventDetails {
232                clone_source_ptr: None,
233                transfer_target_var: None,
234                borrower_scope: Some(borrower_scope.clone()),
235                ref_count_info: None,
236                context: Some("Memory immutably borrowed".to_string()),
237            },
238            OwnershipEventType::MutablyBorrowed { borrower_scope } => OwnershipEventDetails {
239                clone_source_ptr: None,
240                transfer_target_var: None,
241                borrower_scope: Some(borrower_scope.clone()),
242                ref_count_info: None,
243                context: Some("Memory mutably borrowed".to_string()),
244            },
245            OwnershipEventType::BorrowReleased { borrower_scope } => OwnershipEventDetails {
246                clone_source_ptr: None,
247                transfer_target_var: None,
248                borrower_scope: Some(borrower_scope.clone()),
249                ref_count_info: None,
250                context: Some("Borrow released".to_string()),
251            },
252            OwnershipEventType::RefCountChanged {
253                old_count,
254                new_count,
255            } => OwnershipEventDetails {
256                clone_source_ptr: None,
257                transfer_target_var: None,
258                borrower_scope: None,
259                ref_count_info: Some(RefCountInfo {
260                    strong_count: *new_count,
261                    weak_count: 0, // Would need to be provided separately
262                    data_ptr: 0,   // Would need to be provided separately
263                }),
264                context: Some(format!(
265                    "Reference count changed from {old_count} to {new_count}",
266                )),
267            },
268            _ => OwnershipEventDetails {
269                clone_source_ptr: None,
270                transfer_target_var: None,
271                borrower_scope: None,
272                ref_count_info: None,
273                context: None,
274            },
275        }
276    }
277
278    /// Update the ownership summary for an allocation
279    fn update_ownership_summary(
280        &mut self,
281        ptr: usize,
282        event_type: &OwnershipEventType,
283        timestamp: u64,
284    ) {
285        let summary = self
286            .ownership_summaries
287            .entry(ptr)
288            .or_insert_with(|| OwnershipSummary {
289                allocation_ptr: ptr,
290                lifetime_ms: None,
291                borrow_info: BorrowInfo {
292                    immutable_borrows: 0,
293                    mutable_borrows: 0,
294                    max_concurrent_borrows: 0,
295                    last_borrow_timestamp: None,
296                    active_borrows: Vec::new(),
297                },
298                clone_info: CloneInfo {
299                    clone_count: 0,
300                    is_clone: false,
301                    original_ptr: None,
302                    cloned_ptrs: Vec::new(),
303                },
304                ownership_history_available: true,
305                total_events: 0,
306            });
307
308        summary.total_events += 1;
309
310        match event_type {
311            OwnershipEventType::Borrowed { borrower_scope } => {
312                summary.borrow_info.immutable_borrows += 1;
313                summary.borrow_info.last_borrow_timestamp = Some(timestamp);
314                summary.borrow_info.active_borrows.push(ActiveBorrow {
315                    borrower_scope: borrower_scope.clone(),
316                    borrow_type: BorrowType::Immutable,
317                    start_timestamp: timestamp,
318                });
319                summary.borrow_info.max_concurrent_borrows = summary
320                    .borrow_info
321                    .max_concurrent_borrows
322                    .max(summary.borrow_info.active_borrows.len() as u32);
323            }
324            OwnershipEventType::MutablyBorrowed { borrower_scope } => {
325                summary.borrow_info.mutable_borrows += 1;
326                summary.borrow_info.last_borrow_timestamp = Some(timestamp);
327                summary.borrow_info.active_borrows.push(ActiveBorrow {
328                    borrower_scope: borrower_scope.clone(),
329                    borrow_type: BorrowType::Mutable,
330                    start_timestamp: timestamp,
331                });
332                summary.borrow_info.max_concurrent_borrows = summary
333                    .borrow_info
334                    .max_concurrent_borrows
335                    .max(summary.borrow_info.active_borrows.len() as u32);
336            }
337            OwnershipEventType::BorrowReleased { borrower_scope } => {
338                // Remove the corresponding active borrow
339                summary
340                    .borrow_info
341                    .active_borrows
342                    .retain(|borrow| borrow.borrower_scope != *borrower_scope);
343            }
344            OwnershipEventType::Cloned { source_ptr } => {
345                summary.clone_info.is_clone = true;
346                summary.clone_info.original_ptr = Some(*source_ptr);
347
348                // Update the source allocation's clone info
349                if let Some(source_summary) = self.ownership_summaries.get_mut(source_ptr) {
350                    source_summary.clone_info.clone_count += 1;
351                    source_summary.clone_info.cloned_ptrs.push(ptr);
352                }
353            }
354            _ => {
355                // Other events don't need special summary updates
356            }
357        }
358    }
359
360    /// Get ownership events for a specific allocation
361    pub fn get_events(&self, ptr: usize) -> Option<&Vec<OwnershipEvent>> {
362        self.ownership_events.get(&ptr)
363    }
364
365    /// Get ownership summary for a specific allocation
366    pub fn get_summary(&self, ptr: usize) -> Option<&OwnershipSummary> {
367        self.ownership_summaries.get(&ptr)
368    }
369
370    /// Get all ownership summaries
371    pub fn get_all_summaries(&self) -> &HashMap<usize, OwnershipSummary> {
372        &self.ownership_summaries
373    }
374
375    /// Export ownership history to JSON format
376    pub fn export_to_json(&self) -> serde_json::Result<String> {
377        let export_data = OwnershipHistoryExport {
378            summaries: self.ownership_summaries.clone(),
379            detailed_events: self.ownership_events.clone(),
380            export_timestamp: self.get_current_timestamp(),
381            config: self.config.clone(),
382        };
383
384        serde_json::to_string_pretty(&export_data)
385    }
386
387    /// Clear all ownership history
388    pub fn clear(&mut self) {
389        self.ownership_events.clear();
390        self.ownership_summaries.clear();
391    }
392
393    /// Get statistics about the ownership history
394    pub fn get_statistics(&self) -> OwnershipStatistics {
395        let total_allocations = self.ownership_summaries.len();
396        let total_events = self
397            .ownership_events
398            .values()
399            .map(|events| events.len())
400            .sum();
401
402        let mut event_type_counts = HashMap::new();
403        for events in self.ownership_events.values() {
404            for event in events {
405                let event_type_name = match &event.event_type {
406                    OwnershipEventType::Allocated => "Allocated",
407                    OwnershipEventType::Cloned { .. } => "Cloned",
408                    OwnershipEventType::Dropped => "Dropped",
409                    OwnershipEventType::OwnershipTransferred { .. } => "OwnershipTransferred",
410                    OwnershipEventType::Borrowed { .. } => "Borrowed",
411                    OwnershipEventType::MutablyBorrowed { .. } => "MutablyBorrowed",
412                    OwnershipEventType::BorrowReleased { .. } => "BorrowReleased",
413                    OwnershipEventType::RefCountChanged { .. } => "RefCountChanged",
414                };
415                *event_type_counts
416                    .entry(event_type_name.to_string())
417                    .or_insert(0) += 1;
418            }
419        }
420
421        let cloned_allocations = self
422            .ownership_summaries
423            .values()
424            .filter(|summary| summary.clone_info.is_clone)
425            .count();
426
427        let allocations_with_borrows = self
428            .ownership_summaries
429            .values()
430            .filter(|summary| {
431                summary.borrow_info.immutable_borrows > 0 || summary.borrow_info.mutable_borrows > 0
432            })
433            .count();
434
435        OwnershipStatistics {
436            total_allocations,
437            total_events,
438            event_type_counts,
439            cloned_allocations,
440            allocations_with_borrows,
441            average_events_per_allocation: if total_allocations > 0 {
442                total_events as f64 / total_allocations as f64
443            } else {
444                0.0
445            },
446        }
447    }
448
449    /// Get current timestamp in nanoseconds
450    fn get_current_timestamp(&self) -> u64 {
451        std::time::SystemTime::now()
452            .duration_since(std::time::UNIX_EPOCH)
453            .unwrap_or_default()
454            .as_nanos() as u64
455    }
456}
457
458impl Default for OwnershipHistoryRecorder {
459    fn default() -> Self {
460        Self::new()
461    }
462}
463
464/// Export format for ownership history
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct OwnershipHistoryExport {
467    pub summaries: HashMap<usize, OwnershipSummary>,
468    pub detailed_events: HashMap<usize, Vec<OwnershipEvent>>,
469    pub export_timestamp: u64,
470    pub config: HistoryConfig,
471}
472
473/// Statistics about ownership history
474#[derive(Debug, Clone, Serialize)]
475pub struct OwnershipStatistics {
476    pub total_allocations: usize,
477    pub total_events: usize,
478    pub event_type_counts: HashMap<String, usize>,
479    pub cloned_allocations: usize,
480    pub allocations_with_borrows: usize,
481    pub average_events_per_allocation: f64,
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487
488    #[test]
489    fn test_ownership_history_recorder_creation() {
490        let recorder = OwnershipHistoryRecorder::new();
491        assert_eq!(recorder.ownership_events.len(), 0);
492        assert_eq!(recorder.ownership_summaries.len(), 0);
493    }
494
495    #[test]
496    fn test_record_allocation_event() {
497        let mut recorder = OwnershipHistoryRecorder::new();
498        let ptr = 0x1000;
499
500        recorder.record_event(ptr, OwnershipEventType::Allocated, 1);
501
502        let events = recorder.get_events(ptr).expect("Failed to get events");
503        assert_eq!(events.len(), 1);
504        assert!(matches!(
505            events[0].event_type,
506            OwnershipEventType::Allocated
507        ));
508
509        let summary = recorder.get_summary(ptr).expect("Failed to get summary");
510        assert_eq!(summary.allocation_ptr, ptr);
511        assert_eq!(summary.total_events, 1);
512    }
513
514    #[test]
515    fn test_record_clone_event() {
516        let mut recorder = OwnershipHistoryRecorder::new();
517        let source_ptr = 0x1000;
518        let clone_ptr = 0x2000;
519
520        // Record allocation for source
521        recorder.record_event(source_ptr, OwnershipEventType::Allocated, 1);
522
523        // Record clone event
524        recorder.record_event(clone_ptr, OwnershipEventType::Cloned { source_ptr }, 2);
525
526        let clone_summary = recorder
527            .get_summary(clone_ptr)
528            .expect("Failed to get clone summary");
529        assert!(clone_summary.clone_info.is_clone);
530        assert_eq!(clone_summary.clone_info.original_ptr, Some(source_ptr));
531
532        let source_summary = recorder
533            .get_summary(source_ptr)
534            .expect("Failed to get source summary");
535        assert_eq!(source_summary.clone_info.clone_count, 1);
536        assert!(source_summary.clone_info.cloned_ptrs.contains(&clone_ptr));
537    }
538
539    #[test]
540    fn test_record_borrow_events() {
541        let mut recorder = OwnershipHistoryRecorder::new();
542        let ptr = 0x1000;
543
544        recorder.record_event(ptr, OwnershipEventType::Allocated, 1);
545        recorder.record_event(
546            ptr,
547            OwnershipEventType::Borrowed {
548                borrower_scope: "scope1".to_string(),
549            },
550            2,
551        );
552        recorder.record_event(
553            ptr,
554            OwnershipEventType::MutablyBorrowed {
555                borrower_scope: "scope2".to_string(),
556            },
557            3,
558        );
559
560        let summary = recorder.get_summary(ptr).expect("Test operation failed");
561        assert_eq!(summary.borrow_info.immutable_borrows, 1);
562        assert_eq!(summary.borrow_info.mutable_borrows, 1);
563        assert_eq!(summary.borrow_info.max_concurrent_borrows, 2);
564        assert_eq!(summary.borrow_info.active_borrows.len(), 2);
565    }
566
567    #[test]
568    fn test_borrow_release() {
569        let mut recorder = OwnershipHistoryRecorder::new();
570        let ptr = 0x1000;
571
572        recorder.record_event(ptr, OwnershipEventType::Allocated, 1);
573        recorder.record_event(
574            ptr,
575            OwnershipEventType::Borrowed {
576                borrower_scope: "scope1".to_string(),
577            },
578            2,
579        );
580        recorder.record_event(
581            ptr,
582            OwnershipEventType::BorrowReleased {
583                borrower_scope: "scope1".to_string(),
584            },
585            3,
586        );
587
588        let summary = recorder.get_summary(ptr).expect("Test operation failed");
589        assert_eq!(summary.borrow_info.immutable_borrows, 1);
590        assert_eq!(summary.borrow_info.active_borrows.len(), 0);
591    }
592
593    #[test]
594    fn test_event_limit() {
595        let config = HistoryConfig {
596            max_events_per_allocation: 3,
597            ..Default::default()
598        };
599        let mut recorder = OwnershipHistoryRecorder::with_config(config);
600        let ptr = 0x1000;
601
602        // Record more events than the limit
603        for i in 0..5 {
604            recorder.record_event(ptr, OwnershipEventType::Allocated, i as u32);
605        }
606
607        let events = recorder.get_events(ptr).expect("Failed to get events");
608        assert_eq!(events.len(), 3); // Should be limited to 3
609    }
610
611    #[test]
612    fn test_statistics() {
613        let mut recorder = OwnershipHistoryRecorder::new();
614
615        recorder.record_event(0x1000, OwnershipEventType::Allocated, 1);
616        recorder.record_event(0x2000, OwnershipEventType::Cloned { source_ptr: 0x1000 }, 2);
617        recorder.record_event(
618            0x1000,
619            OwnershipEventType::Borrowed {
620                borrower_scope: "scope1".to_string(),
621            },
622            3,
623        );
624
625        let stats = recorder.get_statistics();
626        assert_eq!(stats.total_allocations, 2);
627        assert_eq!(stats.total_events, 3);
628        assert_eq!(stats.cloned_allocations, 1);
629        assert_eq!(stats.allocations_with_borrows, 1);
630    }
631
632    #[test]
633    fn test_json_export() {
634        let mut recorder = OwnershipHistoryRecorder::new();
635        recorder.record_event(0x1000, OwnershipEventType::Allocated, 1);
636
637        let json = recorder.export_to_json().expect("Failed to export to JSON");
638        assert!(json.contains("summaries"));
639        assert!(json.contains("detailed_events"));
640        assert!(json.contains("export_timestamp"));
641    }
642}