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)]
150pub struct AllocationInfo {
151 pub ptr: usize,
153 pub size: usize,
155 pub source: AllocationSource,
157 pub allocated_at_ms: u64,
159 pub deallocated_at_ms: Option<u64>,
161 pub is_active: bool,
163}
164
165impl AllocationInfo {
166 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 fn now_ms() -> u64 {
181 SystemTime::now()
182 .duration_since(UNIX_EPOCH)
183 .unwrap_or_default()
184 .as_millis() as u64
185 }
186
187 pub fn mark_deallocated(&mut self) {
189 self.deallocated_at_ms = Some(Self::now_ms());
190 self.is_active = false;
191 }
192
193 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 pub fn is_leaked(&self, threshold_ms: u64) -> bool {
201 self.is_active && self.age_ms() > threshold_ms
202 }
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct MemoryPassport {
208 pub passport_id: String,
210 pub origin: AllocationOrigin,
212 pub journey: Vec<PassportStamp>,
214 pub current_owner: OwnershipInfo,
216 pub validity_status: ValidityStatus,
218 pub security_clearance: SecurityClearance,
220}
221
222impl MemoryPassport {
223 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 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 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 pub fn revoke(&mut self) {
255 self.validity_status = ValidityStatus::Revoked;
256 }
257
258 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#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct AllocationOrigin {
270 pub context: String,
272 pub allocator_function: String,
274 pub timestamp: u64,
276}
277
278impl AllocationOrigin {
279 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#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct PassportStamp {
301 pub timestamp: u64,
303 pub location: String,
305 pub operation: String,
307 pub verification_hash: String,
309}
310
311impl PassportStamp {
312 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#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct OwnershipInfo {
333 pub owner_context: String,
335 pub owner_function: String,
337 pub transfer_timestamp: u64,
339}
340
341impl OwnershipInfo {
342 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 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
375pub enum ValidityStatus {
376 Valid,
378 Expired,
380 Revoked,
382 Suspicious,
384}
385
386#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
388pub enum SecurityClearance {
389 Public,
391 Restricted,
393 Confidential,
395 Secret,
397}
398
399#[derive(Debug, Clone, Default, Serialize, Deserialize)]
401pub struct UnsafeTrackingStats {
402 pub total_allocations: usize,
404 pub unsafe_allocations: usize,
406 pub ffi_allocations: usize,
408 pub total_bytes_tracked: usize,
410 pub active_allocations: usize,
412 pub total_violations: usize,
414 pub double_free_count: usize,
416 pub invalid_free_count: usize,
418 pub leak_count: usize,
420 pub active_passports: usize,
422}
423
424impl UnsafeTrackingStats {
425 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
443pub 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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), deallocated_at_ms: None,
791 is_active: true,
792 };
793 assert!(old_info.is_leaked(1000));
794
795 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)), 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 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), deallocated_at_ms: None,
938 is_active: true,
939 };
940
941 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}