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