Skip to main content

memscope_rs/analysis/
memory_passport_tracker.rs

1//! Memory Passport Tracking System for FFI Boundary Memory Management
2//!
3//! This module implements comprehensive memory passport tracking for memory that crosses
4//! FFI boundaries, including lifecycle event recording and leak detection at shutdown.
5
6use crate::analysis::unsafe_ffi_tracker::StackFrame;
7use crate::capture::types::{TrackingError, TrackingResult};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicU32, Ordering};
11use std::sync::{Arc, Mutex};
12use std::time::{SystemTime, UNIX_EPOCH};
13
14/// Memory passport for tracking cross-FFI boundary memory
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct MemoryPassport {
17    /// Unique passport identifier
18    pub passport_id: String,
19    /// Memory allocation pointer
20    pub allocation_ptr: usize,
21    /// Size in bytes
22    pub size_bytes: usize,
23    /// Type name of the allocated memory
24    pub type_name: String,
25    /// Variable name if available
26    pub var_name: String,
27    /// Current status at program shutdown
28    pub status_at_shutdown: PassportStatus,
29    /// Complete lifecycle events recorded
30    pub lifecycle_events: Vec<PassportEvent>,
31    /// Creation timestamp
32    pub created_at: u64,
33    /// Last update timestamp
34    pub updated_at: u64,
35    /// Additional metadata
36    pub metadata: HashMap<String, String>,
37}
38
39/// Status of memory passport at program shutdown
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41pub enum PassportStatus {
42    /// Memory properly freed by Rust
43    FreedByRust,
44    /// Memory handed over to FFI and not returned
45    HandoverToFfi,
46    /// Memory freed by foreign code
47    FreedByForeign,
48    /// Memory reclaimed by Rust from FFI
49    ReclaimedByRust,
50    /// Memory still in foreign custody (confirmed leak)
51    InForeignCustody,
52    /// Status unknown or corrupted
53    Unknown,
54}
55
56/// Lifecycle event in memory passport
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
58pub struct PassportEvent {
59    /// Event type
60    pub event_type: PassportEventType,
61    /// Timestamp of event
62    pub timestamp: u64,
63    /// Context where event occurred
64    pub context: String,
65    /// Call stack at event time
66    pub call_stack: Vec<StackFrame>,
67    /// Additional event metadata
68    pub metadata: HashMap<String, String>,
69    /// Event sequence number
70    pub sequence_number: u32,
71}
72
73/// Types of passport events for lifecycle tracking
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
75pub enum PassportEventType {
76    /// Memory allocated in Rust
77    AllocatedInRust,
78    /// Memory handed over to FFI (HandoverToFfi)
79    HandoverToFfi,
80    /// Memory freed by foreign code (FreedByForeign)
81    FreedByForeign,
82    /// Memory reclaimed by Rust (ReclaimedByRust)
83    ReclaimedByRust,
84    /// Memory accessed across boundary
85    BoundaryAccess,
86    /// Memory ownership transferred
87    OwnershipTransfer,
88    /// Memory validation check
89    ValidationCheck,
90    /// Memory corruption detected
91    CorruptionDetected,
92}
93
94/// Memory passport tracker for comprehensive FFI boundary tracking
95pub struct MemoryPassportTracker {
96    /// Active memory passports
97    passports: Arc<Mutex<HashMap<usize, MemoryPassport>>>,
98    /// Passport creation sequence
99    sequence_counter: Arc<Mutex<u32>>,
100    /// Event sequence counter (use AtomicU32 for lock-free operation)
101    event_sequence: Arc<AtomicU32>,
102    /// Tracker configuration
103    config: PassportTrackerConfig,
104    /// Statistics tracking
105    stats: Arc<Mutex<PassportTrackerStats>>,
106}
107
108/// Configuration for passport tracker
109#[derive(Debug, Clone)]
110pub struct PassportTrackerConfig {
111    /// Enable detailed event logging
112    pub detailed_logging: bool,
113    /// Maximum number of events per passport
114    pub max_events_per_passport: usize,
115    /// Enable automatic leak detection
116    pub enable_leak_detection: bool,
117    /// Enable passport validation
118    pub enable_validation: bool,
119    /// Maximum number of passports to track
120    pub max_passports: usize,
121    /// Track Rust internal call stacks (e.g., std, core, alloc)
122    /// When false, only user code call stacks are tracked
123    pub track_rust_internal_stack: bool,
124    /// User code path prefixes for identifying user code
125    /// Call stacks from paths NOT starting with these prefixes are considered Rust internal
126    /// If empty, all paths are considered user code
127    pub user_code_prefixes: Vec<String>,
128}
129
130impl Default for PassportTrackerConfig {
131    fn default() -> Self {
132        Self {
133            detailed_logging: true,
134            max_events_per_passport: 100,
135            enable_leak_detection: true,
136            enable_validation: true,
137            max_passports: 10000,
138            track_rust_internal_stack: false,
139            user_code_prefixes: vec![
140                "src/".to_string(),
141                "examples/".to_string(),
142                "tests/".to_string(),
143                "benches/".to_string(),
144            ],
145        }
146    }
147}
148
149/// Statistics for passport tracker
150#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151pub struct PassportTrackerStats {
152    /// Total passports created
153    pub total_passports_created: usize,
154    /// Active passports
155    pub active_passports: usize,
156    /// Passports by status
157    pub passports_by_status: HashMap<String, usize>,
158    /// Total events recorded
159    pub total_events_recorded: usize,
160    /// Leaks detected at shutdown
161    pub leaks_detected: usize,
162    /// Validation failures
163    pub validation_failures: usize,
164    /// Tracker start time
165    pub tracker_start_time: u64,
166}
167
168/// Leak detection result
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct LeakDetectionResult {
171    /// Leaked passport IDs
172    pub leaked_passports: Vec<String>,
173    /// Total leaks detected
174    pub total_leaks: usize,
175    /// Leak details
176    pub leak_details: Vec<LeakDetail>,
177    /// Detection timestamp
178    pub detected_at: u64,
179}
180
181/// Details of a detected leak
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct LeakDetail {
184    /// Passport ID
185    pub passport_id: String,
186    /// Memory address
187    pub memory_address: usize,
188    /// Size of leaked memory
189    pub size_bytes: usize,
190    /// Last known context
191    pub last_context: String,
192    /// Time since last event
193    pub time_since_last_event: u64,
194    /// Lifecycle summary
195    pub lifecycle_summary: String,
196}
197
198impl MemoryPassportTracker {
199    /// Create new memory passport tracker
200    pub fn new(config: PassportTrackerConfig) -> Self {
201        tracing::debug!("Initializing Memory Passport Tracker");
202        tracing::debug!("   Detailed logging: {}", config.detailed_logging);
203        tracing::debug!(
204            "   Max events per passport: {}",
205            config.max_events_per_passport
206        );
207        tracing::debug!("   Leak detection: {}", config.enable_leak_detection);
208        tracing::debug!("   Validation: {}", config.enable_validation);
209
210        Self {
211            passports: Arc::new(Mutex::new(HashMap::new())),
212            sequence_counter: Arc::new(Mutex::new(0)),
213            event_sequence: Arc::new(AtomicU32::new(0)),
214            config,
215            stats: Arc::new(Mutex::new(PassportTrackerStats {
216                tracker_start_time: SystemTime::now()
217                    .duration_since(UNIX_EPOCH)
218                    .unwrap_or_default()
219                    .as_secs(),
220                ..Default::default()
221            })),
222        }
223    }
224
225    /// Create memory passport for FFI boundary tracking (backward compatible).
226    ///
227    /// This is a convenience wrapper that calls `create_passport` with
228    /// `type_name` and `var_name` set to `None`.
229    pub fn create_passport_simple(
230        &self,
231        allocation_ptr: usize,
232        size_bytes: usize,
233        initial_context: String,
234    ) -> TrackingResult<String> {
235        self.create_passport(allocation_ptr, size_bytes, initial_context, None, None)
236    }
237
238    /// Create memory passport for FFI boundary tracking
239    pub fn create_passport(
240        &self,
241        allocation_ptr: usize,
242        size_bytes: usize,
243        initial_context: String,
244        type_name: Option<String>,
245        var_name: Option<String>,
246    ) -> TrackingResult<String> {
247        let passport_id = self.generate_passport_id(allocation_ptr)?;
248        let current_time = SystemTime::now()
249            .duration_since(UNIX_EPOCH)
250            .unwrap_or_default()
251            .as_secs();
252
253        // Create initial event
254        let initial_event = PassportEvent {
255            event_type: PassportEventType::AllocatedInRust,
256            timestamp: current_time,
257            context: initial_context,
258            call_stack: self.capture_call_stack()?,
259            metadata: HashMap::new(),
260            sequence_number: self.get_next_event_sequence(),
261        };
262
263        let passport = MemoryPassport {
264            passport_id: passport_id.clone(),
265            allocation_ptr,
266            size_bytes,
267            type_name: type_name.unwrap_or_else(|| "-".to_string()),
268            var_name: var_name.unwrap_or_else(|| "-".to_string()),
269            status_at_shutdown: PassportStatus::Unknown,
270            lifecycle_events: vec![initial_event],
271            created_at: current_time,
272            updated_at: current_time,
273            metadata: HashMap::new(),
274        };
275
276        // Store passport
277        if let Ok(mut passports) = self.passports.lock() {
278            // Check passport limit
279            if passports.len() >= self.config.max_passports {
280                return Err(TrackingError::ResourceExhausted(
281                    "Maximum passport limit reached".to_string(),
282                ));
283            }
284            passports.insert(allocation_ptr, passport);
285        } else {
286            return Err(TrackingError::LockContention(
287                "Failed to lock passports".to_string(),
288            ));
289        }
290
291        // Update statistics
292        self.update_stats_passport_created();
293
294        if self.config.detailed_logging {
295            tracing::debug!(
296                "Created passport: {} for 0x{:x} ({} bytes)",
297                passport_id,
298                allocation_ptr,
299                size_bytes
300            );
301        }
302
303        Ok(passport_id)
304    }
305
306    /// Create memory passport with automatic type inference
307    pub fn create_passport_with_inference(
308        &self,
309        allocation_ptr: usize,
310        size_bytes: usize,
311        memory: Option<&[u8]>,
312        initial_context: String,
313        var_name: Option<String>,
314    ) -> TrackingResult<String> {
315        let type_name = memory
316            .map(|m| {
317                let guess =
318                    crate::analysis::unsafe_inference::UnsafeInferenceEngine::infer_from_bytes(
319                        m, size_bytes,
320                    );
321                guess.display_with_confidence()
322            })
323            .unwrap_or_else(|| "-".to_string());
324
325        self.create_passport(
326            allocation_ptr,
327            size_bytes,
328            initial_context,
329            Some(type_name),
330            var_name,
331        )
332    }
333
334    /// Record HandoverToFfi event
335    pub fn record_handover_to_ffi(
336        &self,
337        allocation_ptr: usize,
338        ffi_context: String,
339        function_name: String,
340    ) -> TrackingResult<()> {
341        let mut metadata = HashMap::new();
342        metadata.insert("ffi_function".to_string(), function_name);
343
344        self.record_passport_event(
345            allocation_ptr,
346            PassportEventType::HandoverToFfi,
347            ffi_context,
348            metadata,
349        )
350    }
351
352    /// Record FreedByForeign event
353    pub fn record_freed_by_foreign(
354        &self,
355        allocation_ptr: usize,
356        foreign_context: String,
357        free_function: String,
358    ) -> TrackingResult<()> {
359        let mut metadata = HashMap::new();
360        metadata.insert("free_function".to_string(), free_function);
361
362        self.record_passport_event(
363            allocation_ptr,
364            PassportEventType::FreedByForeign,
365            foreign_context,
366            metadata,
367        )
368    }
369
370    /// Record ReclaimedByRust event
371    pub fn record_reclaimed_by_rust(
372        &self,
373        allocation_ptr: usize,
374        rust_context: String,
375        reclaim_reason: String,
376    ) -> TrackingResult<()> {
377        let mut metadata = HashMap::new();
378        metadata.insert("reclaim_reason".to_string(), reclaim_reason);
379
380        self.record_passport_event(
381            allocation_ptr,
382            PassportEventType::ReclaimedByRust,
383            rust_context,
384            metadata,
385        )
386    }
387
388    /// Record general passport event
389    pub fn record_passport_event(
390        &self,
391        allocation_ptr: usize,
392        event_type: PassportEventType,
393        context: String,
394        metadata: HashMap<String, String>,
395    ) -> TrackingResult<()> {
396        let current_time = SystemTime::now()
397            .duration_since(UNIX_EPOCH)
398            .unwrap_or_default()
399            .as_secs();
400
401        let event = PassportEvent {
402            event_type: event_type.clone(),
403            timestamp: current_time,
404            context: context.clone(),
405            call_stack: self.capture_call_stack()?,
406            metadata,
407            sequence_number: self.get_next_event_sequence(),
408        };
409
410        if let Ok(mut passports) = self.passports.lock() {
411            if let Some(passport) = passports.get_mut(&allocation_ptr) {
412                // Limit events per passport
413                if passport.lifecycle_events.len() >= self.config.max_events_per_passport {
414                    passport.lifecycle_events.drain(0..1); // Remove oldest event
415                }
416
417                passport.lifecycle_events.push(event);
418                passport.updated_at = current_time;
419
420                // Update statistics
421                self.update_stats_event_recorded();
422
423                if self.config.detailed_logging {
424                    tracing::debug!(
425                        "Recorded {:?} event for passport 0x{:x} in context: {}",
426                        event_type,
427                        allocation_ptr,
428                        context
429                    );
430                }
431            } else {
432                return Err(TrackingError::InvalidPointer(format!(
433                    "No passport found for 0x{allocation_ptr:x}"
434                )));
435            }
436        } else {
437            return Err(TrackingError::LockContention(
438                "Failed to lock passports".to_string(),
439            ));
440        }
441
442        Ok(())
443    }
444
445    /// Detect InForeignCustody status and memory leaks at program shutdown
446    pub fn detect_leaks_at_shutdown(&self) -> LeakDetectionResult {
447        if !self.config.enable_leak_detection {
448            return LeakDetectionResult {
449                leaked_passports: Vec::new(),
450                total_leaks: 0,
451                leak_details: Vec::new(),
452                detected_at: SystemTime::now()
453                    .duration_since(UNIX_EPOCH)
454                    .unwrap_or_default()
455                    .as_secs(),
456            };
457        }
458
459        let mut leaked_passports = Vec::new();
460        let mut leak_details = Vec::new();
461        let current_time = SystemTime::now()
462            .duration_since(UNIX_EPOCH)
463            .unwrap_or_default()
464            .as_secs();
465
466        if let Ok(mut passports) = self.passports.lock() {
467            for (ptr, passport) in passports.iter_mut() {
468                // Determine final status based on lifecycle events
469                let final_status = self.determine_final_status(&passport.lifecycle_events);
470                passport.status_at_shutdown = final_status.clone();
471
472                // Check for leaks (InForeignCustody status)
473                if final_status == PassportStatus::InForeignCustody {
474                    leaked_passports.push(passport.passport_id.clone());
475
476                    // Create leak detail
477                    let last_event = passport.lifecycle_events.last();
478                    let last_context = last_event
479                        .map(|e| e.context.clone())
480                        .unwrap_or_else(|| "unknown".to_string());
481                    let time_since_last = last_event
482                        .map(|e| current_time.saturating_sub(e.timestamp))
483                        .unwrap_or(0);
484
485                    let lifecycle_summary =
486                        self.create_lifecycle_summary(&passport.lifecycle_events);
487
488                    leak_details.push(LeakDetail {
489                        passport_id: passport.passport_id.clone(),
490                        memory_address: *ptr,
491                        size_bytes: passport.size_bytes,
492                        last_context,
493                        time_since_last_event: time_since_last,
494                        lifecycle_summary,
495                    });
496
497                    tracing::warn!("🚨 MEMORY LEAK DETECTED: Passport {} (0x{:x}, {} bytes) in foreign custody", 
498                        passport.passport_id, ptr, passport.size_bytes);
499                }
500            }
501
502            // Update statistics
503            if let Ok(mut stats) = self.stats.lock() {
504                stats.leaks_detected = leaked_passports.len();
505
506                // Update status counts
507                stats.passports_by_status.clear();
508                for passport in passports.values() {
509                    let status_key = format!("{:?}", passport.status_at_shutdown);
510                    *stats.passports_by_status.entry(status_key).or_insert(0) += 1;
511                }
512            }
513        }
514
515        let total_leaks = leaked_passports.len();
516
517        tracing::debug!(
518            "Leak detection complete: {} leaks detected out of {} passports",
519            total_leaks,
520            self.get_passport_count()
521        );
522
523        LeakDetectionResult {
524            leaked_passports,
525            total_leaks,
526            leak_details,
527            detected_at: current_time,
528        }
529    }
530
531    /// Get passport by allocation pointer
532    pub fn get_passport(&self, allocation_ptr: usize) -> Option<MemoryPassport> {
533        self.passports.lock().ok()?.get(&allocation_ptr).cloned()
534    }
535
536    /// Get all passports
537    pub fn get_all_passports(&self) -> HashMap<usize, MemoryPassport> {
538        self.passports
539            .lock()
540            .unwrap_or_else(|e| {
541                tracing::warn!(
542                    "Mutex poisoned in get_all_passports, recovering data: {}",
543                    e
544                );
545                e.into_inner()
546            })
547            .clone()
548    }
549
550    /// Get passports by status
551    pub fn get_passports_by_status(&self, status: PassportStatus) -> Vec<MemoryPassport> {
552        if let Ok(passports) = self.passports.lock() {
553            passports
554                .values()
555                .filter(|p| p.status_at_shutdown == status)
556                .cloned()
557                .collect()
558        } else {
559            Vec::new()
560        }
561    }
562
563    /// Get tracker statistics
564    pub fn get_stats(&self) -> PassportTrackerStats {
565        self.stats
566            .lock()
567            .unwrap_or_else(|e| {
568                tracing::warn!("Mutex poisoned in get_stats, recovering data: {}", e);
569                e.into_inner()
570            })
571            .clone()
572    }
573
574    /// Validate passport integrity
575    pub fn validate_passport(&self, allocation_ptr: usize) -> TrackingResult<bool> {
576        if !self.config.enable_validation {
577            return Ok(true);
578        }
579
580        if let Ok(passports) = self.passports.lock() {
581            if let Some(passport) = passports.get(&allocation_ptr) {
582                // Validate event sequence
583                let mut last_sequence = 0;
584                for event in &passport.lifecycle_events {
585                    if event.sequence_number < last_sequence {
586                        self.update_stats_validation_failure();
587                        return Ok(false);
588                    }
589                    last_sequence = event.sequence_number;
590                }
591
592                // Validate timestamps
593                let mut last_timestamp = 0;
594                for event in &passport.lifecycle_events {
595                    if event.timestamp < last_timestamp {
596                        self.update_stats_validation_failure();
597                        return Ok(false);
598                    }
599                    last_timestamp = event.timestamp;
600                }
601
602                Ok(true)
603            } else {
604                Err(TrackingError::InvalidPointer(format!(
605                    "No passport found for 0x{allocation_ptr:x}",
606                )))
607            }
608        } else {
609            Err(TrackingError::LockContention(
610                "Failed to lock passports".to_string(),
611            ))
612        }
613    }
614
615    /// Clear all passports (for testing)
616    pub fn clear_all_passports(&self) {
617        if let Ok(mut passports) = self.passports.lock() {
618            passports.clear();
619        }
620
621        if let Ok(mut stats) = self.stats.lock() {
622            *stats = PassportTrackerStats {
623                tracker_start_time: stats.tracker_start_time,
624                ..Default::default()
625            };
626        }
627
628        tracing::debug!("Cleared all passports");
629    }
630
631    // Private helper methods
632
633    fn generate_passport_id(&self, allocation_ptr: usize) -> TrackingResult<String> {
634        let sequence = if let Ok(mut counter) = self.sequence_counter.lock() {
635            *counter += 1;
636            *counter
637        } else {
638            return Err(TrackingError::LockContention(
639                "Failed to lock sequence counter".to_string(),
640            ));
641        };
642
643        let timestamp = SystemTime::now()
644            .duration_since(UNIX_EPOCH)
645            .unwrap_or_default()
646            .as_nanos();
647
648        Ok(format!(
649            "passport_{:x}_{:08x}_{}",
650            allocation_ptr,
651            sequence,
652            timestamp % 1000000
653        ))
654    }
655
656    fn get_next_event_sequence(&self) -> u32 {
657        // Use atomic fetch_add for lock-free sequence number generation
658        // This eliminates lock contention and prevents ID conflicts
659        self.event_sequence.fetch_add(1, Ordering::SeqCst)
660    }
661
662    fn capture_call_stack(&self) -> TrackingResult<Vec<StackFrame>> {
663        // Use backtrace crate to capture real call stack
664        #[cfg(feature = "backtrace")]
665        {
666            let backtrace = backtrace::Backtrace::new();
667            let mut frames = Vec::new();
668
669            // Skip first 2 frames (capture_call_stack and caller)
670            let skip_frames = 2;
671            let max_depth = 32; // Maximum stack depth to capture
672
673            for (i, frame) in backtrace.frames().iter().enumerate() {
674                if i < skip_frames {
675                    continue;
676                }
677
678                if let Some(symbol) = frame.symbols().first() {
679                    frames.push(StackFrame {
680                        function_name: symbol
681                            .name()
682                            .map(|n| format!("{:?}", n))
683                            .unwrap_or_else(|| "unknown".to_string()),
684                        file_name: symbol.filename().map(|p| p.display().to_string()),
685                        line_number: symbol.lineno(),
686                        is_unsafe: false,
687                    });
688                }
689
690                if frames.len() >= max_depth {
691                    break;
692                }
693            }
694
695            Ok(frames)
696        }
697
698        #[cfg(not(feature = "backtrace"))]
699        {
700            // Return empty stack when backtrace feature is not enabled
701            Ok(Vec::new())
702        }
703    }
704
705    fn determine_final_status(&self, events: &[PassportEvent]) -> PassportStatus {
706        let mut has_handover = false;
707        let mut has_reclaim = false;
708        let mut has_foreign_free = false;
709        let mut has_corruption = false;
710
711        for event in events {
712            match event.event_type {
713                PassportEventType::HandoverToFfi => has_handover = true,
714                PassportEventType::ReclaimedByRust => {
715                    has_reclaim = true;
716                    has_handover = false;
717                }
718                PassportEventType::FreedByForeign => {
719                    has_foreign_free = true;
720                    has_handover = false;
721                }
722                PassportEventType::CorruptionDetected => has_corruption = true,
723                _ => {}
724            }
725        }
726
727        if has_corruption {
728            PassportStatus::Unknown
729        } else if has_handover && !has_reclaim && !has_foreign_free {
730            PassportStatus::InForeignCustody
731        } else if has_foreign_free {
732            PassportStatus::FreedByForeign
733        } else if has_reclaim {
734            PassportStatus::ReclaimedByRust
735        } else if has_handover {
736            PassportStatus::HandoverToFfi
737        } else {
738            PassportStatus::FreedByRust
739        }
740    }
741
742    fn create_lifecycle_summary(&self, events: &[PassportEvent]) -> String {
743        let event_types: Vec<String> = events
744            .iter()
745            .map(|e| format!("{:?}", e.event_type))
746            .collect();
747
748        if event_types.is_empty() {
749            "No events recorded".to_string()
750        } else {
751            format!("Events: {}", event_types.join(" -> "))
752        }
753    }
754
755    fn get_passport_count(&self) -> usize {
756        self.passports.lock().map(|p| p.len()).unwrap_or(0)
757    }
758
759    fn update_stats_passport_created(&self) {
760        if let Ok(mut stats) = self.stats.lock() {
761            stats.total_passports_created += 1;
762            stats.active_passports = self.get_passport_count();
763        }
764    }
765
766    fn update_stats_event_recorded(&self) {
767        if let Ok(mut stats) = self.stats.lock() {
768            stats.total_events_recorded += 1;
769        }
770    }
771
772    fn update_stats_validation_failure(&self) {
773        if let Ok(mut stats) = self.stats.lock() {
774            stats.validation_failures += 1;
775        }
776    }
777}
778
779impl Default for MemoryPassportTracker {
780    fn default() -> Self {
781        Self::new(PassportTrackerConfig::default())
782    }
783}
784
785/// Global memory passport tracker instance (used as fallback for direct access)
786static GLOBAL_MEMORY_PASSPORT_TRACKER: std::sync::OnceLock<Arc<MemoryPassportTracker>> =
787    std::sync::OnceLock::new();
788
789/// Get global memory passport tracker instance
790///
791/// # Note
792/// This function is deprecated. Use `global_passport_tracker()` from
793/// `memscope_rs::capture::backends::global_tracking` instead.
794pub fn get_global_passport_tracker() -> Arc<MemoryPassportTracker> {
795    GLOBAL_MEMORY_PASSPORT_TRACKER
796        .get_or_init(|| Arc::new(MemoryPassportTracker::new(PassportTrackerConfig::default())))
797        .clone()
798}
799
800/// Initialize global passport tracker with custom config
801///
802/// # Note  
803/// This function is deprecated. Use `init_global_tracking_with_config()` from
804/// `memscope_rs::capture::backends::global_tracking` instead.
805pub fn initialize_global_passport_tracker(
806    config: PassportTrackerConfig,
807) -> Arc<MemoryPassportTracker> {
808    GLOBAL_MEMORY_PASSPORT_TRACKER
809        .get_or_init(|| Arc::new(MemoryPassportTracker::new(config)))
810        .clone()
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816
817    #[test]
818    fn test_passport_creation() {
819        let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
820        let ptr = 0x1000;
821        let size = 1024;
822
823        let passport_id = tracker
824            .create_passport(ptr, size, "test_context".to_string(), None, None)
825            .expect("Failed to create passport");
826        assert!(!passport_id.is_empty());
827
828        let passport = tracker.get_passport(ptr).expect("Failed to get passport");
829        assert_eq!(passport.allocation_ptr, ptr);
830        assert_eq!(passport.size_bytes, size);
831        assert_eq!(passport.lifecycle_events.len(), 1);
832        assert_eq!(
833            passport.lifecycle_events[0].event_type,
834            PassportEventType::AllocatedInRust
835        );
836    }
837
838    #[test]
839    fn test_handover_to_ffi() {
840        let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
841        let ptr = 0x2000;
842
843        tracker
844            .create_passport(ptr, 512, "rust_context".to_string(), None, None)
845            .expect("Failed to create passport");
846        tracker
847            .record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
848            .expect("Failed to record handover");
849
850        let passport = tracker.get_passport(ptr).expect("Failed to get passport");
851        assert_eq!(passport.lifecycle_events.len(), 2);
852        assert_eq!(
853            passport.lifecycle_events[1].event_type,
854            PassportEventType::HandoverToFfi
855        );
856    }
857
858    #[test]
859    fn test_leak_detection() {
860        let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
861        let ptr = 0x3000;
862
863        // Create passport and hand over to FFI without reclaim
864        tracker
865            .create_passport(ptr, 256, "rust_context".to_string(), None, None)
866            .expect("Failed to create passport");
867        tracker
868            .record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
869            .expect("Failed to record handover");
870
871        // Detect leaks at shutdown
872        let leak_result = tracker.detect_leaks_at_shutdown();
873
874        assert_eq!(leak_result.total_leaks, 1);
875        assert_eq!(leak_result.leaked_passports.len(), 1);
876        assert_eq!(leak_result.leak_details.len(), 1);
877        assert_eq!(leak_result.leak_details[0].memory_address, ptr);
878
879        // Verify passport status
880        let passport = tracker.get_passport(ptr).expect("Test operation failed");
881        assert_eq!(
882            passport.status_at_shutdown,
883            PassportStatus::InForeignCustody
884        );
885    }
886
887    #[test]
888    fn test_reclaim_prevents_leak() {
889        let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
890        let ptr = 0x4000;
891
892        // Create passport, hand over to FFI, then reclaim
893        tracker
894            .create_passport(ptr, 128, "rust_context".to_string(), None, None)
895            .expect("Failed to create passport");
896        tracker
897            .record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
898            .expect("Failed to record handover");
899        tracker
900            .record_reclaimed_by_rust(ptr, "rust_context".to_string(), "cleanup".to_string())
901            .expect("Failed to record reclaim");
902
903        // Detect leaks at shutdown
904        let leak_result = tracker.detect_leaks_at_shutdown();
905
906        assert_eq!(leak_result.total_leaks, 0);
907
908        // Verify passport status
909        let passport = tracker.get_passport(ptr).expect("Test operation failed");
910        assert_eq!(passport.status_at_shutdown, PassportStatus::ReclaimedByRust);
911    }
912
913    #[test]
914    fn test_passport_validation() {
915        let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
916        let ptr = 0x5000;
917
918        tracker
919            .create_passport(ptr, 64, "test_context".to_string(), None, None)
920            .expect("Failed to create passport");
921        tracker
922            .record_handover_to_ffi(ptr, "ffi_context".to_string(), "test_func".to_string())
923            .expect("Failed to record handover");
924
925        let is_valid = tracker
926            .validate_passport(ptr)
927            .expect("Failed to validate passport");
928        assert!(is_valid);
929    }
930}