1use 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#[derive(Debug, Clone)]
14pub struct UnsafeTrackingConfig {
15 pub track_unsafe_allocations: bool,
17 pub track_ffi_allocations: bool,
19 pub detect_double_free: bool,
21 pub detect_memory_leaks: bool,
23 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 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 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub enum AllocationSource {
66 SafeRust,
68 UnsafeRust { location: String },
70 Ffi { library: String, function: String },
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub enum SafetyViolation {
77 DoubleFree {
79 ptr: usize,
80 first_free_time_ms: u64,
81 second_free_time_ms: u64,
82 },
83 InvalidFree { ptr: usize, time_ms: u64 },
85 PotentialLeak {
87 ptr: usize,
88 allocation_time_ms: u64,
89 detection_time_ms: u64,
90 size: usize,
91 },
92 CrossBoundaryRisk {
94 ptr: usize,
95 from_context: String,
96 to_context: String,
97 risk_level: String,
98 },
99}
100
101impl SafetyViolation {
102 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
141pub enum ViolationSeverity {
142 Low,
143 Medium,
144 High,
145 Critical,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct AllocationInfo {
156 pub ptr: usize,
158 pub size: usize,
160 pub source: AllocationSource,
162 pub allocated_at_ms: u64,
164 pub deallocated_at_ms: Option<u64>,
166 pub is_active: bool,
168}
169
170impl AllocationInfo {
171 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 fn now_ms() -> u64 {
186 SystemTime::now()
187 .duration_since(UNIX_EPOCH)
188 .unwrap_or_default()
189 .as_millis() as u64
190 }
191
192 pub fn mark_deallocated(&mut self) {
194 self.deallocated_at_ms = Some(Self::now_ms());
195 self.is_active = false;
196 }
197
198 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 pub fn is_leaked(&self, threshold_ms: u64) -> bool {
206 self.is_active && self.age_ms() > threshold_ms
207 }
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct MemoryPassport {
213 pub passport_id: String,
215 pub origin: AllocationOrigin,
217 pub journey: Vec<PassportStamp>,
219 pub current_owner: OwnershipInfo,
221 pub validity_status: ValidityStatus,
223 pub security_clearance: SecurityClearance,
225}
226
227impl MemoryPassport {
228 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 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 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 pub fn revoke(&mut self) {
260 self.validity_status = ValidityStatus::Revoked;
261 }
262
263 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#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct AllocationOrigin {
275 pub context: String,
277 pub allocator_function: String,
279 pub timestamp: u64,
281}
282
283impl AllocationOrigin {
284 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#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct PassportStamp {
306 pub timestamp: u64,
308 pub location: String,
310 pub operation: String,
312 pub verification_hash: String,
314}
315
316impl PassportStamp {
317 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#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct OwnershipInfo {
338 pub owner_context: String,
340 pub owner_function: String,
342 pub transfer_timestamp: u64,
344}
345
346impl OwnershipInfo {
347 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 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
380pub enum ValidityStatus {
381 Valid,
383 Expired,
385 Revoked,
387 Suspicious,
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
393pub enum SecurityClearance {
394 Public,
396 Restricted,
398 Confidential,
400 Secret,
402}
403
404#[derive(Debug, Clone, Default, Serialize, Deserialize)]
406pub struct UnsafeTrackingStats {
407 pub total_allocations: usize,
409 pub unsafe_allocations: usize,
411 pub ffi_allocations: usize,
413 pub total_bytes_tracked: usize,
415 pub active_allocations: usize,
417 pub total_violations: usize,
419 pub double_free_count: usize,
421 pub invalid_free_count: usize,
423 pub leak_count: usize,
425 pub active_passports: usize,
427}
428
429impl UnsafeTrackingStats {
430 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
448pub 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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), deallocated_at_ms: None,
796 is_active: true,
797 };
798 assert!(old_info.is_leaked(1000));
799
800 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)), 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 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), deallocated_at_ms: None,
943 is_active: true,
944 };
945
946 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}