Skip to main content

memscope_rs/capture/backends/
unsafe_tracking.rs

1//! Unsafe/FFI tracking for unified tracker
2//!
3//! This module provides comprehensive unsafe Rust and FFI memory tracking
4//! with the innovative Memory Passport system for cross-boundary tracking.
5
6use crate::core::error::{MemScopeError, MemoryOperation, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12/// Unsafe/FFI tracking configuration
13#[derive(Debug, Clone)]
14pub struct UnsafeTrackingConfig {
15    /// Enable tracking of unsafe Rust allocations
16    pub track_unsafe_allocations: bool,
17    /// Enable tracking of FFI allocations
18    pub track_ffi_allocations: bool,
19    /// Enable double-free detection
20    pub detect_double_free: bool,
21    /// Enable memory leak detection
22    pub detect_memory_leaks: bool,
23    /// Enable cross-boundary tracking with memory passport
24    pub enable_memory_passport: bool,
25}
26
27impl Default for UnsafeTrackingConfig {
28    fn default() -> Self {
29        Self {
30            track_unsafe_allocations: true,
31            track_ffi_allocations: true,
32            detect_double_free: true,
33            detect_memory_leaks: true,
34            enable_memory_passport: true,
35        }
36    }
37}
38
39impl UnsafeTrackingConfig {
40    /// Create configuration for minimal tracking
41    pub fn minimal() -> Self {
42        Self {
43            track_unsafe_allocations: true,
44            track_ffi_allocations: true,
45            detect_double_free: false,
46            detect_memory_leaks: false,
47            enable_memory_passport: false,
48        }
49    }
50
51    /// Create configuration for comprehensive tracking
52    pub fn comprehensive() -> Self {
53        Self {
54            track_unsafe_allocations: true,
55            track_ffi_allocations: true,
56            detect_double_free: true,
57            detect_memory_leaks: true,
58            enable_memory_passport: true,
59        }
60    }
61}
62
63/// Allocation source type
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub enum AllocationSource {
66    /// Safe Rust allocation
67    SafeRust,
68    /// Unsafe Rust allocation
69    UnsafeRust { location: String },
70    /// FFI allocation
71    Ffi { library: String, function: String },
72}
73
74/// Safety violation types
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub enum SafetyViolation {
77    /// Double free detected
78    DoubleFree {
79        ptr: usize,
80        first_free_time_ms: u64,
81        second_free_time_ms: u64,
82    },
83    /// Invalid free (pointer not in allocation table)
84    InvalidFree { ptr: usize, time_ms: u64 },
85    /// Potential memory leak
86    PotentialLeak {
87        ptr: usize,
88        allocation_time_ms: u64,
89        detection_time_ms: u64,
90        size: usize,
91    },
92    /// Cross-boundary risk detected
93    CrossBoundaryRisk {
94        ptr: usize,
95        from_context: String,
96        to_context: String,
97        risk_level: String,
98    },
99}
100
101impl SafetyViolation {
102    /// Get violation severity
103    pub fn severity(&self) -> ViolationSeverity {
104        match self {
105            Self::DoubleFree { .. } => ViolationSeverity::Critical,
106            Self::InvalidFree { .. } => ViolationSeverity::High,
107            Self::PotentialLeak { .. } => ViolationSeverity::Medium,
108            Self::CrossBoundaryRisk { .. } => ViolationSeverity::High,
109        }
110    }
111
112    /// Get human-readable description
113    pub fn description(&self) -> String {
114        match self {
115            Self::DoubleFree { ptr, .. } => {
116                format!("Double free detected at pointer 0x{:x}", ptr)
117            }
118            Self::InvalidFree { ptr, .. } => {
119                format!("Invalid free of pointer 0x{:x} (not allocated)", ptr)
120            }
121            Self::PotentialLeak { ptr, size, .. } => {
122                format!("Potential leak of {} bytes at pointer 0x{:x}", size, ptr)
123            }
124            Self::CrossBoundaryRisk {
125                ptr,
126                from_context,
127                to_context,
128                ..
129            } => {
130                format!(
131                    "Cross-boundary risk at 0x{:x}: {} -> {}",
132                    ptr, from_context, to_context
133                )
134            }
135        }
136    }
137}
138
139/// Violation severity levels
140#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
141pub enum ViolationSeverity {
142    Low,
143    Medium,
144    High,
145    Critical,
146}
147
148/// Allocation information
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct AllocationInfo {
151    /// Memory pointer
152    pub ptr: usize,
153    /// Allocation size in bytes
154    pub size: usize,
155    /// Allocation source
156    pub source: AllocationSource,
157    /// Allocation timestamp (milliseconds since Unix epoch)
158    pub allocated_at_ms: u64,
159    /// Deallocation timestamp (milliseconds since Unix epoch, if deallocated)
160    pub deallocated_at_ms: Option<u64>,
161    /// Whether this allocation is currently active
162    pub is_active: bool,
163}
164
165impl AllocationInfo {
166    /// Create new allocation info
167    pub fn new(ptr: usize, size: usize, source: AllocationSource) -> Self {
168        let now_ms = Self::now_ms();
169        Self {
170            ptr,
171            size,
172            source,
173            allocated_at_ms: now_ms,
174            deallocated_at_ms: None,
175            is_active: true,
176        }
177    }
178
179    /// Get current timestamp in milliseconds
180    fn now_ms() -> u64 {
181        SystemTime::now()
182            .duration_since(UNIX_EPOCH)
183            .unwrap_or_default()
184            .as_millis() as u64
185    }
186
187    /// Mark as deallocated
188    pub fn mark_deallocated(&mut self) {
189        self.deallocated_at_ms = Some(Self::now_ms());
190        self.is_active = false;
191    }
192
193    /// Get allocation age in milliseconds
194    pub fn age_ms(&self) -> u64 {
195        let now_ms = Self::now_ms();
196        now_ms.saturating_sub(self.allocated_at_ms)
197    }
198
199    /// Check if allocation is leaked (active for too long)
200    pub fn is_leaked(&self, threshold_ms: u64) -> bool {
201        self.is_active && self.age_ms() > threshold_ms
202    }
203}
204
205/// Memory "passport" for tracking cross-boundary transfers
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct MemoryPassport {
208    /// Unique identifier for this memory passport
209    pub passport_id: String,
210    /// Original allocation context
211    pub origin: AllocationOrigin,
212    /// Journey of the memory through different contexts
213    pub journey: Vec<PassportStamp>,
214    /// Current ownership information
215    pub current_owner: OwnershipInfo,
216    /// Validity status of passport
217    pub validity_status: ValidityStatus,
218    /// Security clearance level
219    pub security_clearance: SecurityClearance,
220}
221
222impl MemoryPassport {
223    /// Create new memory passport
224    pub fn new(ptr: usize, size: usize, source: AllocationSource) -> Self {
225        let passport_id = format!("passport_{:x}_{}", ptr, Self::now_ms());
226
227        let origin = AllocationOrigin::new(ptr, size, source.clone());
228        let owner = OwnershipInfo::new(source);
229
230        Self {
231            passport_id,
232            origin,
233            journey: Vec::new(),
234            current_owner: owner,
235            validity_status: ValidityStatus::Valid,
236            security_clearance: SecurityClearance::Public,
237        }
238    }
239
240    /// Add a journey stamp
241    pub fn add_stamp(&mut self, location: String, operation: String) {
242        let stamp = PassportStamp::new(location, operation);
243        self.journey.push(stamp);
244    }
245
246    /// Transfer ownership
247    pub fn transfer_ownership(&mut self, new_context: String, new_function: String) {
248        let context_clone = new_context.clone();
249        self.current_owner = OwnershipInfo::with_context(new_context, new_function);
250        self.add_stamp(context_clone, "ownership_transfer".to_string());
251    }
252
253    /// Revoke passport (memory freed)
254    pub fn revoke(&mut self) {
255        self.validity_status = ValidityStatus::Revoked;
256    }
257
258    /// Get current timestamp in milliseconds
259    fn now_ms() -> u64 {
260        SystemTime::now()
261            .duration_since(UNIX_EPOCH)
262            .unwrap_or_default()
263            .as_millis() as u64
264    }
265}
266
267/// Information about where memory was originally allocated
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct AllocationOrigin {
270    /// Context where allocation occurred (Rust/FFI)
271    pub context: String,
272    /// Function that performed allocation
273    pub allocator_function: String,
274    /// Timestamp of original allocation
275    pub timestamp: u64,
276}
277
278impl AllocationOrigin {
279    /// Create new allocation origin
280    pub fn new(_ptr: usize, _size: usize, source: AllocationSource) -> Self {
281        let (context, function) = match source {
282            AllocationSource::SafeRust => ("rust_safe".to_string(), "alloc".to_string()),
283            AllocationSource::UnsafeRust { location } => ("rust_unsafe".to_string(), location),
284            AllocationSource::Ffi { library, function } => (library, function),
285        };
286
287        Self {
288            context,
289            allocator_function: function,
290            timestamp: SystemTime::now()
291                .duration_since(UNIX_EPOCH)
292                .unwrap_or_default()
293                .as_millis() as u64,
294        }
295    }
296}
297
298/// A stamp in the memory passport journey
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct PassportStamp {
301    /// Timestamp of this checkpoint
302    pub timestamp: u64,
303    /// Location/context of checkpoint
304    pub location: String,
305    /// Operation performed at this checkpoint
306    pub operation: String,
307    /// Cryptographic hash for verification
308    pub verification_hash: String,
309}
310
311impl PassportStamp {
312    /// Create new passport stamp
313    pub fn new(location: String, operation: String) -> Self {
314        let timestamp = SystemTime::now()
315            .duration_since(UNIX_EPOCH)
316            .unwrap_or_default()
317            .as_millis() as u64;
318
319        let verification_hash = format!("{}:{}:{}", timestamp, location, operation);
320
321        Self {
322            timestamp,
323            location,
324            operation,
325            verification_hash,
326        }
327    }
328}
329
330/// Current ownership information
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct OwnershipInfo {
333    /// Current owner context (Rust/FFI)
334    pub owner_context: String,
335    /// Function/module that owns the memory
336    pub owner_function: String,
337    /// Ownership transfer timestamp
338    pub transfer_timestamp: u64,
339}
340
341impl OwnershipInfo {
342    /// Create new ownership info
343    pub fn new(source: AllocationSource) -> Self {
344        let (context, function) = match source {
345            AllocationSource::SafeRust => ("rust_safe".to_string(), "alloc".to_string()),
346            AllocationSource::UnsafeRust { location } => ("rust_unsafe".to_string(), location),
347            AllocationSource::Ffi { library, function } => (library, function),
348        };
349
350        Self {
351            owner_context: context,
352            owner_function: function,
353            transfer_timestamp: SystemTime::now()
354                .duration_since(UNIX_EPOCH)
355                .unwrap_or_default()
356                .as_millis() as u64,
357        }
358    }
359
360    /// Create ownership info with specific context
361    pub fn with_context(context: String, function: String) -> Self {
362        Self {
363            owner_context: context,
364            owner_function: function,
365            transfer_timestamp: SystemTime::now()
366                .duration_since(UNIX_EPOCH)
367                .unwrap_or_default()
368                .as_millis() as u64,
369        }
370    }
371}
372
373/// Validity status of a memory passport
374#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
375pub enum ValidityStatus {
376    /// Passport is valid and memory is safe to use
377    Valid,
378    /// Passport is expired (memory may be freed)
379    Expired,
380    /// Passport is revoked (memory is definitely freed)
381    Revoked,
382    /// Passport validity is unknown/suspicious
383    Suspicious,
384}
385
386/// Security clearance levels for memory operations
387#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
388pub enum SecurityClearance {
389    /// Public memory, safe for all operations
390    Public,
391    /// Restricted memory, limited operations allowed
392    Restricted,
393    /// Confidential memory, special handling required
394    Confidential,
395    /// Secret memory, maximum security required
396    Secret,
397}
398
399/// Unsafe/FFI tracking statistics
400#[derive(Debug, Clone, Default, Serialize, Deserialize)]
401pub struct UnsafeTrackingStats {
402    /// Total number of allocations tracked
403    pub total_allocations: usize,
404    /// Number of unsafe Rust allocations
405    pub unsafe_allocations: usize,
406    /// Number of FFI allocations
407    pub ffi_allocations: usize,
408    /// Total bytes tracked
409    pub total_bytes_tracked: usize,
410    /// Current active allocations
411    pub active_allocations: usize,
412    /// Number of safety violations
413    pub total_violations: usize,
414    /// Number of double-free violations
415    pub double_free_count: usize,
416    /// Number of invalid-free violations
417    pub invalid_free_count: usize,
418    /// Number of potential leaks
419    pub leak_count: usize,
420    /// Number of active memory passports
421    pub active_passports: usize,
422}
423
424impl UnsafeTrackingStats {
425    /// Get a human-readable summary
426    pub fn summary(&self) -> String {
427        format!(
428            "Allocations: {} (Unsafe: {}, FFI: {}), Bytes: {}, Active: {}, Violations: {} (Double-free: {}, Invalid-free: {}, Leaks: {}), Passports: {}",
429            self.total_allocations,
430            self.unsafe_allocations,
431            self.ffi_allocations,
432            self.total_bytes_tracked,
433            self.active_allocations,
434            self.total_violations,
435            self.double_free_count,
436            self.invalid_free_count,
437            self.leak_count,
438            self.active_passports
439        )
440    }
441}
442
443/// Simplified unsafe/FFI tracker with memory passport system
444pub struct UnsafeTracker {
445    config: UnsafeTrackingConfig,
446    allocations: Arc<Mutex<HashMap<usize, AllocationInfo>>>,
447    passports: Arc<Mutex<HashMap<usize, MemoryPassport>>>,
448    violations: Arc<Mutex<Vec<SafetyViolation>>>,
449}
450
451impl UnsafeTracker {
452    /// Create new unsafe tracker with default configuration
453    pub fn new() -> Self {
454        Self {
455            config: UnsafeTrackingConfig::default(),
456            allocations: Arc::new(Mutex::new(HashMap::new())),
457            passports: Arc::new(Mutex::new(HashMap::new())),
458            violations: Arc::new(Mutex::new(Vec::new())),
459        }
460    }
461
462    /// Create new unsafe tracker with custom configuration
463    pub fn with_config(config: UnsafeTrackingConfig) -> Self {
464        Self {
465            config,
466            allocations: Arc::new(Mutex::new(HashMap::new())),
467            passports: Arc::new(Mutex::new(HashMap::new())),
468            violations: Arc::new(Mutex::new(Vec::new())),
469        }
470    }
471
472    /// Track an unsafe Rust allocation
473    pub fn track_unsafe_allocation(&self, ptr: usize, size: usize, location: String) -> Result<()> {
474        if !self.config.track_unsafe_allocations {
475            return Ok(());
476        }
477
478        let source = AllocationSource::UnsafeRust { location };
479        let allocation = AllocationInfo::new(ptr, size, source.clone());
480
481        if let Ok(mut passports) = self.passports.lock() {
482            if self.config.enable_memory_passport {
483                let passport = MemoryPassport::new(ptr, size, source.clone());
484                passports.insert(ptr, passport);
485            }
486        }
487
488        if let Ok(mut allocations) = self.allocations.lock() {
489            allocations.insert(ptr, allocation);
490            tracing::info!("Tracked unsafe allocation at 0x{:x} (size: {})", ptr, size);
491            Ok(())
492        } else {
493            Err(MemScopeError::system(
494                crate::core::error::SystemErrorType::Locking,
495                "Failed to acquire allocations lock",
496            ))
497        }
498    }
499
500    /// Track an FFI allocation
501    pub fn track_ffi_allocation(
502        &self,
503        ptr: usize,
504        size: usize,
505        library: String,
506        function: String,
507    ) -> Result<()> {
508        if !self.config.track_ffi_allocations {
509            return Ok(());
510        }
511
512        let source = AllocationSource::Ffi {
513            library: library.clone(),
514            function: function.clone(),
515        };
516        let allocation = AllocationInfo::new(ptr, size, source.clone());
517
518        if let Ok(mut passports) = self.passports.lock() {
519            if self.config.enable_memory_passport {
520                let passport = MemoryPassport::new(ptr, size, source);
521                passports.insert(ptr, passport);
522            }
523        }
524
525        if let Ok(mut allocations) = self.allocations.lock() {
526            allocations.insert(ptr, allocation);
527            tracing::info!(
528                "Tracked FFI allocation at 0x{:x} (size: {}, lib: {}, func: {})",
529                ptr,
530                size,
531                library,
532                function
533            );
534            Ok(())
535        } else {
536            Err(MemScopeError::system(
537                crate::core::error::SystemErrorType::Locking,
538                "Failed to acquire allocations lock",
539            ))
540        }
541    }
542
543    /// Track a deallocation
544    pub fn track_deallocation(&self, ptr: usize) -> Result<()> {
545        if let Ok(mut allocations) = self.allocations.lock() {
546            if let Some(mut allocation) = allocations.remove(&ptr) {
547                allocation.mark_deallocated();
548
549                if let Ok(mut passports) = self.passports.lock() {
550                    if let Some(mut passport) = passports.remove(&ptr) {
551                        passport.revoke();
552                    }
553                }
554
555                Ok(())
556            } else {
557                let violation = SafetyViolation::InvalidFree {
558                    ptr,
559                    time_ms: AllocationInfo::now_ms(),
560                };
561
562                if let Ok(mut violations) = self.violations.lock() {
563                    violations.push(violation.clone());
564                }
565
566                tracing::error!("Invalid free detected at 0x{:x}", ptr);
567                Err(MemScopeError::memory_with_context(
568                    MemoryOperation::Deallocation,
569                    format!("Invalid free of pointer 0x{:x}", ptr),
570                    "pointer not in allocation table",
571                ))
572            }
573        } else {
574            Err(MemScopeError::system(
575                crate::core::error::SystemErrorType::Locking,
576                "Failed to acquire allocations lock",
577            ))
578        }
579    }
580
581    /// Record a cross-boundary event
582    pub fn record_boundary_event(
583        &self,
584        ptr: usize,
585        from_context: String,
586        to_context: String,
587    ) -> Result<()> {
588        if let Ok(mut passports) = self.passports.lock() {
589            if let Some(passport) = passports.get_mut(&ptr) {
590                passport.transfer_ownership(to_context.clone(), "boundary_transfer".to_string());
591                tracing::info!(
592                    "Recorded boundary event for 0x{:x}: {} -> {}",
593                    ptr,
594                    from_context,
595                    to_context
596                );
597                Ok(())
598            } else {
599                Err(MemScopeError::memory_with_context(
600                    MemoryOperation::Tracking,
601                    format!("No passport found for pointer 0x{:x}", ptr),
602                    "cross-boundary tracking requires memory passport",
603                ))
604            }
605        } else {
606            Err(MemScopeError::system(
607                crate::core::error::SystemErrorType::Locking,
608                "Failed to acquire passports lock",
609            ))
610        }
611    }
612
613    /// Get all safety violations
614    pub fn get_violations(&self) -> Vec<SafetyViolation> {
615        if let Ok(violations) = self.violations.lock() {
616            violations.clone()
617        } else {
618            Vec::new()
619        }
620    }
621
622    /// Detect memory leaks
623    pub fn detect_leaks(&self, threshold_ms: u64) -> Vec<SafetyViolation> {
624        let mut leaks = Vec::new();
625
626        if let Ok(allocations) = self.allocations.lock() {
627            for allocation in allocations.values() {
628                if allocation.is_leaked(threshold_ms) {
629                    leaks.push(SafetyViolation::PotentialLeak {
630                        ptr: allocation.ptr,
631                        allocation_time_ms: allocation.allocated_at_ms,
632                        detection_time_ms: AllocationInfo::now_ms(),
633                        size: allocation.size,
634                    });
635                }
636            }
637        }
638
639        if !leaks.is_empty() {
640            if let Ok(mut violations) = self.violations.lock() {
641                violations.extend(leaks.clone());
642            }
643        }
644
645        leaks
646    }
647
648    /// Get all allocations
649    pub fn get_allocations(&self) -> Vec<AllocationInfo> {
650        if let Ok(allocations) = self.allocations.lock() {
651            allocations.values().cloned().collect()
652        } else {
653            Vec::new()
654        }
655    }
656
657    /// Get active allocations
658    pub fn get_active_allocations(&self) -> Vec<AllocationInfo> {
659        self.get_allocations()
660            .into_iter()
661            .filter(|alloc| alloc.is_active)
662            .collect()
663    }
664
665    /// Get memory passport for a pointer
666    pub fn get_passport(&self, ptr: usize) -> Option<MemoryPassport> {
667        if let Ok(passports) = self.passports.lock() {
668            passports.get(&ptr).cloned()
669        } else {
670            None
671        }
672    }
673
674    /// Get all active passports
675    pub fn get_active_passports(&self) -> Vec<MemoryPassport> {
676        if let Ok(passports) = self.passports.lock() {
677            passports
678                .values()
679                .filter(|p| p.validity_status == ValidityStatus::Valid)
680                .cloned()
681                .collect()
682        } else {
683            Vec::new()
684        }
685    }
686
687    /// Get tracking statistics
688    pub fn get_stats(&self) -> UnsafeTrackingStats {
689        let allocations = self.get_allocations();
690        let violations = self.get_violations();
691        let active_passports = self.get_active_passports();
692
693        let unsafe_count = allocations
694            .iter()
695            .filter(|alloc| matches!(alloc.source, AllocationSource::UnsafeRust { .. }))
696            .count();
697
698        let ffi_count = allocations
699            .iter()
700            .filter(|alloc| matches!(alloc.source, AllocationSource::Ffi { .. }))
701            .count();
702
703        let total_size: usize = allocations.iter().map(|alloc| alloc.size).sum();
704
705        let active_count = allocations.iter().filter(|alloc| alloc.is_active).count();
706
707        let double_free_count = violations
708            .iter()
709            .filter(|v| matches!(v, SafetyViolation::DoubleFree { .. }))
710            .count();
711
712        let invalid_free_count = violations
713            .iter()
714            .filter(|v| matches!(v, SafetyViolation::InvalidFree { .. }))
715            .count();
716
717        let leak_count = violations
718            .iter()
719            .filter(|v| matches!(v, SafetyViolation::PotentialLeak { .. }))
720            .count();
721
722        UnsafeTrackingStats {
723            total_allocations: allocations.len(),
724            unsafe_allocations: unsafe_count,
725            ffi_allocations: ffi_count,
726            total_bytes_tracked: total_size,
727            active_allocations: active_count,
728            total_violations: violations.len(),
729            double_free_count,
730            invalid_free_count,
731            leak_count,
732            active_passports: active_passports.len(),
733        }
734    }
735
736    /// Clear all tracking data
737    pub fn clear(&self) {
738        if let Ok(mut allocations) = self.allocations.lock() {
739            allocations.clear();
740        }
741        if let Ok(mut passports) = self.passports.lock() {
742            passports.clear();
743        }
744        if let Ok(mut violations) = self.violations.lock() {
745            violations.clear();
746        }
747    }
748}
749
750impl Default for UnsafeTracker {
751    fn default() -> Self {
752        Self::new()
753    }
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    #[test]
761    fn test_allocation_info_creation() {
762        let info = AllocationInfo::new(0x1000, 1024, AllocationSource::SafeRust);
763        assert_eq!(info.ptr, 0x1000);
764        assert_eq!(info.size, 1024);
765        assert!(info.is_active);
766        assert!(info.deallocated_at_ms.is_none());
767    }
768
769    #[test]
770    fn test_allocation_info_deallocation() {
771        let mut info = AllocationInfo::new(0x1000, 1024, AllocationSource::SafeRust);
772        assert!(info.is_active);
773        info.mark_deallocated();
774        assert!(!info.is_active);
775        assert!(info.deallocated_at_ms.is_some());
776    }
777
778    #[test]
779    fn test_allocation_info_leak_detection() {
780        let info = AllocationInfo::new(0x1000, 1024, AllocationSource::SafeRust);
781        assert!(!info.is_leaked(1000));
782
783        // Simulate time passing by creating an older allocation
784        let now_ms = AllocationInfo::now_ms();
785        let old_info = AllocationInfo {
786            ptr: 0x1000,
787            size: 1024,
788            source: AllocationSource::SafeRust,
789            allocated_at_ms: now_ms.saturating_sub(2000), // 2 seconds ago
790            deallocated_at_ms: None,
791            is_active: true,
792        };
793        assert!(old_info.is_leaked(1000));
794
795        // Deallocated allocation should not be leaked
796        let deallocated_info = AllocationInfo {
797            ptr: 0x2000,
798            size: 1024,
799            source: AllocationSource::SafeRust,
800            allocated_at_ms: now_ms.saturating_sub(2000),
801            deallocated_at_ms: Some(now_ms.saturating_sub(1000)), // 1 second ago
802            is_active: false,
803        };
804        assert!(!deallocated_info.is_leaked(1000));
805    }
806
807    #[test]
808    fn test_memory_passport_creation() {
809        let passport = MemoryPassport::new(0x1000, 1024, AllocationSource::SafeRust);
810        assert_eq!(passport.validity_status, ValidityStatus::Valid);
811        assert_eq!(passport.security_clearance, SecurityClearance::Public);
812        assert_eq!(passport.journey.len(), 0);
813    }
814
815    #[test]
816    fn test_memory_passport_stamp() {
817        let mut passport = MemoryPassport::new(0x1000, 1024, AllocationSource::SafeRust);
818        passport.add_stamp("test_location".to_string(), "test_operation".to_string());
819        assert_eq!(passport.journey.len(), 1);
820    }
821
822    #[test]
823    fn test_memory_passport_ownership_transfer() {
824        let mut passport = MemoryPassport::new(0x1000, 1024, AllocationSource::SafeRust);
825        passport.transfer_ownership("new_context".to_string(), "new_function".to_string());
826        assert_eq!(passport.current_owner.owner_context, "new_context");
827        assert_eq!(passport.journey.len(), 1);
828    }
829
830    #[test]
831    fn test_memory_passport_revoke() {
832        let mut passport = MemoryPassport::new(0x1000, 1024, AllocationSource::SafeRust);
833        assert_eq!(passport.validity_status, ValidityStatus::Valid);
834        passport.revoke();
835        assert_eq!(passport.validity_status, ValidityStatus::Revoked);
836    }
837
838    #[test]
839    fn test_unsafe_tracker_creation() {
840        let tracker = UnsafeTracker::new();
841        let _ = tracker;
842    }
843
844    #[test]
845    fn test_unsafe_tracker_with_config() {
846        let config = UnsafeTrackingConfig::minimal();
847        let tracker = UnsafeTracker::with_config(config);
848        let _ = tracker;
849    }
850
851    #[test]
852    fn test_track_unsafe_allocation() {
853        let tracker = UnsafeTracker::new();
854        let result = tracker.track_unsafe_allocation(0x1000, 1024, "test.rs:42".to_string());
855        assert!(result.is_ok());
856
857        let allocations = tracker.get_allocations();
858        assert_eq!(allocations.len(), 1);
859        assert_eq!(allocations[0].ptr, 0x1000);
860
861        let passport = tracker.get_passport(0x1000);
862        assert!(passport.is_some());
863    }
864
865    #[test]
866    fn test_track_ffi_allocation() {
867        let tracker = UnsafeTracker::new();
868        let result =
869            tracker.track_ffi_allocation(0x2000, 2048, "libc".to_string(), "malloc".to_string());
870        assert!(result.is_ok());
871
872        let allocations = tracker.get_allocations();
873        assert_eq!(allocations.len(), 1);
874        assert_eq!(allocations[0].ptr, 0x2000);
875
876        let passport = tracker.get_passport(0x2000);
877        assert!(passport.is_some());
878    }
879
880    #[test]
881    fn test_track_deallocation_valid() {
882        let tracker = UnsafeTracker::new();
883        tracker
884            .track_unsafe_allocation(0x1000, 1024, "test.rs:42".to_string())
885            .unwrap();
886
887        let result = tracker.track_deallocation(0x1000);
888        assert!(result.is_ok());
889
890        let allocations = tracker.get_allocations();
891        assert_eq!(allocations.len(), 0);
892
893        let passport = tracker.get_passport(0x1000);
894        assert!(passport.is_none());
895    }
896
897    #[test]
898    fn test_track_deallocation_invalid() {
899        let tracker = UnsafeTracker::new();
900        let result = tracker.track_deallocation(0x1000);
901        assert!(result.is_err());
902
903        let violations = tracker.get_violations();
904        assert_eq!(violations.len(), 1);
905        assert!(matches!(violations[0], SafetyViolation::InvalidFree { .. }));
906    }
907
908    #[test]
909    fn test_record_boundary_event() {
910        let tracker = UnsafeTracker::new();
911        tracker
912            .track_unsafe_allocation(0x1000, 1024, "test.rs:42".to_string())
913            .unwrap();
914
915        let result = tracker.record_boundary_event(0x1000, "rust".to_string(), "ffi".to_string());
916        assert!(result.is_ok());
917
918        let passport = tracker.get_passport(0x1000);
919        assert!(passport.is_some());
920        assert_eq!(passport.unwrap().current_owner.owner_context, "ffi");
921    }
922
923    #[test]
924    fn test_detect_leaks() {
925        let tracker = UnsafeTracker::new();
926        tracker
927            .track_unsafe_allocation(0x1000, 1024, "test.rs:42".to_string())
928            .unwrap();
929
930        // Simulate time passing by creating an old allocation
931        let now_ms = AllocationInfo::now_ms();
932        let old_allocation = AllocationInfo {
933            ptr: 0x1000,
934            size: 1024,
935            source: AllocationSource::SafeRust,
936            allocated_at_ms: now_ms.saturating_sub(15000), // 15 seconds ago
937            deallocated_at_ms: None,
938            is_active: true,
939        };
940
941        // Manually add the old allocation to the tracker
942        if let Ok(mut allocations) = tracker.allocations.lock() {
943            allocations.insert(0x1000, old_allocation);
944        }
945
946        let leaks = tracker.detect_leaks(10000);
947        assert_eq!(leaks.len(), 1);
948        assert!(matches!(leaks[0], SafetyViolation::PotentialLeak { .. }));
949    }
950
951    #[test]
952    fn test_get_stats() {
953        let tracker = UnsafeTracker::new();
954        tracker
955            .track_unsafe_allocation(0x1000, 1024, "test.rs:42".to_string())
956            .unwrap();
957        tracker
958            .track_ffi_allocation(0x2000, 2048, "libc".to_string(), "malloc".to_string())
959            .unwrap();
960
961        let stats = tracker.get_stats();
962        assert_eq!(stats.total_allocations, 2);
963        assert_eq!(stats.unsafe_allocations, 1);
964        assert_eq!(stats.ffi_allocations, 1);
965        assert_eq!(stats.total_bytes_tracked, 3072);
966        assert_eq!(stats.active_allocations, 2);
967        assert_eq!(stats.active_passports, 2);
968    }
969
970    #[test]
971    fn test_clear() {
972        let tracker = UnsafeTracker::new();
973        tracker
974            .track_unsafe_allocation(0x1000, 1024, "test.rs:42".to_string())
975            .unwrap();
976
977        tracker.clear();
978
979        let allocations = tracker.get_allocations();
980        assert_eq!(allocations.len(), 0);
981
982        let violations = tracker.get_violations();
983        assert_eq!(violations.len(), 0);
984
985        let passports = tracker.get_active_passports();
986        assert_eq!(passports.len(), 0);
987    }
988
989    #[test]
990    fn test_violation_severity() {
991        let double_free = SafetyViolation::DoubleFree {
992            ptr: 0x1000,
993            first_free_time_ms: 1000,
994            second_free_time_ms: 2000,
995        };
996        assert_eq!(double_free.severity(), ViolationSeverity::Critical);
997
998        let invalid_free = SafetyViolation::InvalidFree {
999            ptr: 0x1000,
1000            time_ms: 1000,
1001        };
1002        assert_eq!(invalid_free.severity(), ViolationSeverity::High);
1003
1004        let leak = SafetyViolation::PotentialLeak {
1005            ptr: 0x1000,
1006            allocation_time_ms: 1000,
1007            detection_time_ms: 2000,
1008            size: 1024,
1009        };
1010        assert_eq!(leak.severity(), ViolationSeverity::Medium);
1011    }
1012
1013    #[test]
1014    fn test_violation_description() {
1015        let double_free = SafetyViolation::DoubleFree {
1016            ptr: 0x1000,
1017            first_free_time_ms: 1000,
1018            second_free_time_ms: 2000,
1019        };
1020        let desc = double_free.description();
1021        assert!(desc.contains("Double free"));
1022        assert!(desc.contains("0x1000"));
1023    }
1024
1025    #[test]
1026    fn test_stats_summary() {
1027        let stats = UnsafeTrackingStats {
1028            total_allocations: 10,
1029            unsafe_allocations: 3,
1030            ffi_allocations: 4,
1031            total_bytes_tracked: 10240,
1032            active_allocations: 5,
1033            total_violations: 2,
1034            double_free_count: 1,
1035            invalid_free_count: 1,
1036            leak_count: 0,
1037            active_passports: 5,
1038        };
1039        let summary = stats.summary();
1040        assert!(summary.contains("Allocations: 10"));
1041        assert!(summary.contains("Unsafe: 3"));
1042        assert!(summary.contains("FFI: 4"));
1043        assert!(summary.contains("Passports: 5"));
1044    }
1045}