1use crate::analysis::unsafe_ffi_tracker::StackFrame;
7use crate::capture::types::{TrackingError, TrackingResult};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicU32, Ordering};
11use std::sync::{Arc, Mutex};
12use std::time::{SystemTime, UNIX_EPOCH};
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct MemoryPassport {
17 pub passport_id: String,
19 pub allocation_ptr: usize,
21 pub size_bytes: usize,
23 pub type_name: String,
25 pub var_name: String,
27 pub status_at_shutdown: PassportStatus,
29 pub lifecycle_events: Vec<PassportEvent>,
31 pub created_at: u64,
33 pub updated_at: u64,
35 pub metadata: HashMap<String, String>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41pub enum PassportStatus {
42 FreedByRust,
44 HandoverToFfi,
46 FreedByForeign,
48 ReclaimedByRust,
50 InForeignCustody,
52 Unknown,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
58pub struct PassportEvent {
59 pub event_type: PassportEventType,
61 pub timestamp: u64,
63 pub context: String,
65 pub call_stack: Vec<StackFrame>,
67 pub metadata: HashMap<String, String>,
69 pub sequence_number: u32,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
75pub enum PassportEventType {
76 AllocatedInRust,
78 HandoverToFfi,
80 FreedByForeign,
82 ReclaimedByRust,
84 BoundaryAccess,
86 OwnershipTransfer,
88 ValidationCheck,
90 CorruptionDetected,
92}
93
94pub struct MemoryPassportTracker {
96 passports: Arc<Mutex<HashMap<usize, MemoryPassport>>>,
98 sequence_counter: Arc<Mutex<u32>>,
100 event_sequence: Arc<AtomicU32>,
102 config: PassportTrackerConfig,
104 stats: Arc<Mutex<PassportTrackerStats>>,
106}
107
108#[derive(Debug, Clone)]
110pub struct PassportTrackerConfig {
111 pub detailed_logging: bool,
113 pub max_events_per_passport: usize,
115 pub enable_leak_detection: bool,
117 pub enable_validation: bool,
119 pub max_passports: usize,
121 pub track_rust_internal_stack: bool,
124 pub user_code_prefixes: Vec<String>,
128}
129
130impl Default for PassportTrackerConfig {
131 fn default() -> Self {
132 Self {
133 detailed_logging: true,
134 max_events_per_passport: 100,
135 enable_leak_detection: true,
136 enable_validation: true,
137 max_passports: 10000,
138 track_rust_internal_stack: false,
139 user_code_prefixes: vec![
140 "src/".to_string(),
141 "examples/".to_string(),
142 "tests/".to_string(),
143 "benches/".to_string(),
144 ],
145 }
146 }
147}
148
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151pub struct PassportTrackerStats {
152 pub total_passports_created: usize,
154 pub active_passports: usize,
156 pub passports_by_status: HashMap<String, usize>,
158 pub total_events_recorded: usize,
160 pub leaks_detected: usize,
162 pub validation_failures: usize,
164 pub tracker_start_time: u64,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct LeakDetectionResult {
171 pub leaked_passports: Vec<String>,
173 pub total_leaks: usize,
175 pub leak_details: Vec<LeakDetail>,
177 pub detected_at: u64,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct LeakDetail {
184 pub passport_id: String,
186 pub memory_address: usize,
188 pub size_bytes: usize,
190 pub last_context: String,
192 pub time_since_last_event: u64,
194 pub lifecycle_summary: String,
196}
197
198impl MemoryPassportTracker {
199 pub fn new(config: PassportTrackerConfig) -> Self {
201 tracing::debug!("Initializing Memory Passport Tracker");
202 tracing::debug!(" Detailed logging: {}", config.detailed_logging);
203 tracing::debug!(
204 " Max events per passport: {}",
205 config.max_events_per_passport
206 );
207 tracing::debug!(" Leak detection: {}", config.enable_leak_detection);
208 tracing::debug!(" Validation: {}", config.enable_validation);
209
210 Self {
211 passports: Arc::new(Mutex::new(HashMap::new())),
212 sequence_counter: Arc::new(Mutex::new(0)),
213 event_sequence: Arc::new(AtomicU32::new(0)),
214 config,
215 stats: Arc::new(Mutex::new(PassportTrackerStats {
216 tracker_start_time: SystemTime::now()
217 .duration_since(UNIX_EPOCH)
218 .unwrap_or_default()
219 .as_secs(),
220 ..Default::default()
221 })),
222 }
223 }
224
225 pub fn create_passport_simple(
230 &self,
231 allocation_ptr: usize,
232 size_bytes: usize,
233 initial_context: String,
234 ) -> TrackingResult<String> {
235 self.create_passport(allocation_ptr, size_bytes, initial_context, None, None)
236 }
237
238 pub fn create_passport(
240 &self,
241 allocation_ptr: usize,
242 size_bytes: usize,
243 initial_context: String,
244 type_name: Option<String>,
245 var_name: Option<String>,
246 ) -> TrackingResult<String> {
247 let passport_id = self.generate_passport_id(allocation_ptr)?;
248 let current_time = SystemTime::now()
249 .duration_since(UNIX_EPOCH)
250 .unwrap_or_default()
251 .as_secs();
252
253 let initial_event = PassportEvent {
255 event_type: PassportEventType::AllocatedInRust,
256 timestamp: current_time,
257 context: initial_context,
258 call_stack: self.capture_call_stack()?,
259 metadata: HashMap::new(),
260 sequence_number: self.get_next_event_sequence(),
261 };
262
263 let passport = MemoryPassport {
264 passport_id: passport_id.clone(),
265 allocation_ptr,
266 size_bytes,
267 type_name: type_name.unwrap_or_else(|| "-".to_string()),
268 var_name: var_name.unwrap_or_else(|| "-".to_string()),
269 status_at_shutdown: PassportStatus::Unknown,
270 lifecycle_events: vec![initial_event],
271 created_at: current_time,
272 updated_at: current_time,
273 metadata: HashMap::new(),
274 };
275
276 if let Ok(mut passports) = self.passports.lock() {
278 if passports.len() >= self.config.max_passports {
280 return Err(TrackingError::ResourceExhausted(
281 "Maximum passport limit reached".to_string(),
282 ));
283 }
284 passports.insert(allocation_ptr, passport);
285 } else {
286 return Err(TrackingError::LockContention(
287 "Failed to lock passports".to_string(),
288 ));
289 }
290
291 self.update_stats_passport_created();
293
294 if self.config.detailed_logging {
295 tracing::debug!(
296 "Created passport: {} for 0x{:x} ({} bytes)",
297 passport_id,
298 allocation_ptr,
299 size_bytes
300 );
301 }
302
303 Ok(passport_id)
304 }
305
306 pub fn create_passport_with_inference(
308 &self,
309 allocation_ptr: usize,
310 size_bytes: usize,
311 memory: Option<&[u8]>,
312 initial_context: String,
313 var_name: Option<String>,
314 ) -> TrackingResult<String> {
315 let type_name = memory
316 .map(|m| {
317 let guess =
318 crate::analysis::unsafe_inference::UnsafeInferenceEngine::infer_from_bytes(
319 m, size_bytes,
320 );
321 guess.display_with_confidence()
322 })
323 .unwrap_or_else(|| "-".to_string());
324
325 self.create_passport(
326 allocation_ptr,
327 size_bytes,
328 initial_context,
329 Some(type_name),
330 var_name,
331 )
332 }
333
334 pub fn record_handover_to_ffi(
336 &self,
337 allocation_ptr: usize,
338 ffi_context: String,
339 function_name: String,
340 ) -> TrackingResult<()> {
341 let mut metadata = HashMap::new();
342 metadata.insert("ffi_function".to_string(), function_name);
343
344 self.record_passport_event(
345 allocation_ptr,
346 PassportEventType::HandoverToFfi,
347 ffi_context,
348 metadata,
349 )
350 }
351
352 pub fn record_freed_by_foreign(
354 &self,
355 allocation_ptr: usize,
356 foreign_context: String,
357 free_function: String,
358 ) -> TrackingResult<()> {
359 let mut metadata = HashMap::new();
360 metadata.insert("free_function".to_string(), free_function);
361
362 self.record_passport_event(
363 allocation_ptr,
364 PassportEventType::FreedByForeign,
365 foreign_context,
366 metadata,
367 )
368 }
369
370 pub fn record_reclaimed_by_rust(
372 &self,
373 allocation_ptr: usize,
374 rust_context: String,
375 reclaim_reason: String,
376 ) -> TrackingResult<()> {
377 let mut metadata = HashMap::new();
378 metadata.insert("reclaim_reason".to_string(), reclaim_reason);
379
380 self.record_passport_event(
381 allocation_ptr,
382 PassportEventType::ReclaimedByRust,
383 rust_context,
384 metadata,
385 )
386 }
387
388 pub fn record_passport_event(
390 &self,
391 allocation_ptr: usize,
392 event_type: PassportEventType,
393 context: String,
394 metadata: HashMap<String, String>,
395 ) -> TrackingResult<()> {
396 let current_time = SystemTime::now()
397 .duration_since(UNIX_EPOCH)
398 .unwrap_or_default()
399 .as_secs();
400
401 let event = PassportEvent {
402 event_type: event_type.clone(),
403 timestamp: current_time,
404 context: context.clone(),
405 call_stack: self.capture_call_stack()?,
406 metadata,
407 sequence_number: self.get_next_event_sequence(),
408 };
409
410 if let Ok(mut passports) = self.passports.lock() {
411 if let Some(passport) = passports.get_mut(&allocation_ptr) {
412 if passport.lifecycle_events.len() >= self.config.max_events_per_passport {
414 passport.lifecycle_events.drain(0..1); }
416
417 passport.lifecycle_events.push(event);
418 passport.updated_at = current_time;
419
420 self.update_stats_event_recorded();
422
423 if self.config.detailed_logging {
424 tracing::debug!(
425 "Recorded {:?} event for passport 0x{:x} in context: {}",
426 event_type,
427 allocation_ptr,
428 context
429 );
430 }
431 } else {
432 return Err(TrackingError::InvalidPointer(format!(
433 "No passport found for 0x{allocation_ptr:x}"
434 )));
435 }
436 } else {
437 return Err(TrackingError::LockContention(
438 "Failed to lock passports".to_string(),
439 ));
440 }
441
442 Ok(())
443 }
444
445 pub fn detect_leaks_at_shutdown(&self) -> LeakDetectionResult {
447 if !self.config.enable_leak_detection {
448 return LeakDetectionResult {
449 leaked_passports: Vec::new(),
450 total_leaks: 0,
451 leak_details: Vec::new(),
452 detected_at: SystemTime::now()
453 .duration_since(UNIX_EPOCH)
454 .unwrap_or_default()
455 .as_secs(),
456 };
457 }
458
459 let mut leaked_passports = Vec::new();
460 let mut leak_details = Vec::new();
461 let current_time = SystemTime::now()
462 .duration_since(UNIX_EPOCH)
463 .unwrap_or_default()
464 .as_secs();
465
466 if let Ok(mut passports) = self.passports.lock() {
467 for (ptr, passport) in passports.iter_mut() {
468 let final_status = self.determine_final_status(&passport.lifecycle_events);
470 passport.status_at_shutdown = final_status.clone();
471
472 if final_status == PassportStatus::InForeignCustody {
474 leaked_passports.push(passport.passport_id.clone());
475
476 let last_event = passport.lifecycle_events.last();
478 let last_context = last_event
479 .map(|e| e.context.clone())
480 .unwrap_or_else(|| "unknown".to_string());
481 let time_since_last = last_event
482 .map(|e| current_time.saturating_sub(e.timestamp))
483 .unwrap_or(0);
484
485 let lifecycle_summary =
486 self.create_lifecycle_summary(&passport.lifecycle_events);
487
488 leak_details.push(LeakDetail {
489 passport_id: passport.passport_id.clone(),
490 memory_address: *ptr,
491 size_bytes: passport.size_bytes,
492 last_context,
493 time_since_last_event: time_since_last,
494 lifecycle_summary,
495 });
496
497 tracing::warn!("🚨 MEMORY LEAK DETECTED: Passport {} (0x{:x}, {} bytes) in foreign custody",
498 passport.passport_id, ptr, passport.size_bytes);
499 }
500 }
501
502 if let Ok(mut stats) = self.stats.lock() {
504 stats.leaks_detected = leaked_passports.len();
505
506 stats.passports_by_status.clear();
508 for passport in passports.values() {
509 let status_key = format!("{:?}", passport.status_at_shutdown);
510 *stats.passports_by_status.entry(status_key).or_insert(0) += 1;
511 }
512 }
513 }
514
515 let total_leaks = leaked_passports.len();
516
517 tracing::debug!(
518 "Leak detection complete: {} leaks detected out of {} passports",
519 total_leaks,
520 self.get_passport_count()
521 );
522
523 LeakDetectionResult {
524 leaked_passports,
525 total_leaks,
526 leak_details,
527 detected_at: current_time,
528 }
529 }
530
531 pub fn get_passport(&self, allocation_ptr: usize) -> Option<MemoryPassport> {
533 self.passports.lock().ok()?.get(&allocation_ptr).cloned()
534 }
535
536 pub fn get_all_passports(&self) -> HashMap<usize, MemoryPassport> {
538 self.passports
539 .lock()
540 .unwrap_or_else(|e| {
541 tracing::warn!(
542 "Mutex poisoned in get_all_passports, recovering data: {}",
543 e
544 );
545 e.into_inner()
546 })
547 .clone()
548 }
549
550 pub fn get_passports_by_status(&self, status: PassportStatus) -> Vec<MemoryPassport> {
552 if let Ok(passports) = self.passports.lock() {
553 passports
554 .values()
555 .filter(|p| p.status_at_shutdown == status)
556 .cloned()
557 .collect()
558 } else {
559 Vec::new()
560 }
561 }
562
563 pub fn get_stats(&self) -> PassportTrackerStats {
565 self.stats
566 .lock()
567 .unwrap_or_else(|e| {
568 tracing::warn!("Mutex poisoned in get_stats, recovering data: {}", e);
569 e.into_inner()
570 })
571 .clone()
572 }
573
574 pub fn validate_passport(&self, allocation_ptr: usize) -> TrackingResult<bool> {
576 if !self.config.enable_validation {
577 return Ok(true);
578 }
579
580 if let Ok(passports) = self.passports.lock() {
581 if let Some(passport) = passports.get(&allocation_ptr) {
582 let mut last_sequence = 0;
584 for event in &passport.lifecycle_events {
585 if event.sequence_number < last_sequence {
586 self.update_stats_validation_failure();
587 return Ok(false);
588 }
589 last_sequence = event.sequence_number;
590 }
591
592 let mut last_timestamp = 0;
594 for event in &passport.lifecycle_events {
595 if event.timestamp < last_timestamp {
596 self.update_stats_validation_failure();
597 return Ok(false);
598 }
599 last_timestamp = event.timestamp;
600 }
601
602 Ok(true)
603 } else {
604 Err(TrackingError::InvalidPointer(format!(
605 "No passport found for 0x{allocation_ptr:x}",
606 )))
607 }
608 } else {
609 Err(TrackingError::LockContention(
610 "Failed to lock passports".to_string(),
611 ))
612 }
613 }
614
615 pub fn clear_all_passports(&self) {
617 if let Ok(mut passports) = self.passports.lock() {
618 passports.clear();
619 }
620
621 if let Ok(mut stats) = self.stats.lock() {
622 *stats = PassportTrackerStats {
623 tracker_start_time: stats.tracker_start_time,
624 ..Default::default()
625 };
626 }
627
628 tracing::debug!("Cleared all passports");
629 }
630
631 fn generate_passport_id(&self, allocation_ptr: usize) -> TrackingResult<String> {
634 let sequence = if let Ok(mut counter) = self.sequence_counter.lock() {
635 *counter += 1;
636 *counter
637 } else {
638 return Err(TrackingError::LockContention(
639 "Failed to lock sequence counter".to_string(),
640 ));
641 };
642
643 let timestamp = SystemTime::now()
644 .duration_since(UNIX_EPOCH)
645 .unwrap_or_default()
646 .as_nanos();
647
648 Ok(format!(
649 "passport_{:x}_{:08x}_{}",
650 allocation_ptr,
651 sequence,
652 timestamp % 1000000
653 ))
654 }
655
656 fn get_next_event_sequence(&self) -> u32 {
657 self.event_sequence.fetch_add(1, Ordering::SeqCst)
660 }
661
662 fn capture_call_stack(&self) -> TrackingResult<Vec<StackFrame>> {
663 #[cfg(feature = "backtrace")]
665 {
666 let backtrace = backtrace::Backtrace::new();
667 let mut frames = Vec::new();
668
669 let skip_frames = 2;
671 let max_depth = 32; for (i, frame) in backtrace.frames().iter().enumerate() {
674 if i < skip_frames {
675 continue;
676 }
677
678 if let Some(symbol) = frame.symbols().first() {
679 frames.push(StackFrame {
680 function_name: symbol
681 .name()
682 .map(|n| format!("{:?}", n))
683 .unwrap_or_else(|| "unknown".to_string()),
684 file_name: symbol.filename().map(|p| p.display().to_string()),
685 line_number: symbol.lineno(),
686 is_unsafe: false,
687 });
688 }
689
690 if frames.len() >= max_depth {
691 break;
692 }
693 }
694
695 Ok(frames)
696 }
697
698 #[cfg(not(feature = "backtrace"))]
699 {
700 Ok(Vec::new())
702 }
703 }
704
705 fn determine_final_status(&self, events: &[PassportEvent]) -> PassportStatus {
706 let mut has_handover = false;
707 let mut has_reclaim = false;
708 let mut has_foreign_free = false;
709 let mut has_corruption = false;
710
711 for event in events {
712 match event.event_type {
713 PassportEventType::HandoverToFfi => has_handover = true,
714 PassportEventType::ReclaimedByRust => {
715 has_reclaim = true;
716 has_handover = false;
717 }
718 PassportEventType::FreedByForeign => {
719 has_foreign_free = true;
720 has_handover = false;
721 }
722 PassportEventType::CorruptionDetected => has_corruption = true,
723 _ => {}
724 }
725 }
726
727 if has_corruption {
728 PassportStatus::Unknown
729 } else if has_handover && !has_reclaim && !has_foreign_free {
730 PassportStatus::InForeignCustody
731 } else if has_foreign_free {
732 PassportStatus::FreedByForeign
733 } else if has_reclaim {
734 PassportStatus::ReclaimedByRust
735 } else if has_handover {
736 PassportStatus::HandoverToFfi
737 } else {
738 PassportStatus::FreedByRust
739 }
740 }
741
742 fn create_lifecycle_summary(&self, events: &[PassportEvent]) -> String {
743 let event_types: Vec<String> = events
744 .iter()
745 .map(|e| format!("{:?}", e.event_type))
746 .collect();
747
748 if event_types.is_empty() {
749 "No events recorded".to_string()
750 } else {
751 format!("Events: {}", event_types.join(" -> "))
752 }
753 }
754
755 fn get_passport_count(&self) -> usize {
756 self.passports.lock().map(|p| p.len()).unwrap_or(0)
757 }
758
759 fn update_stats_passport_created(&self) {
760 if let Ok(mut stats) = self.stats.lock() {
761 stats.total_passports_created += 1;
762 stats.active_passports = self.get_passport_count();
763 }
764 }
765
766 fn update_stats_event_recorded(&self) {
767 if let Ok(mut stats) = self.stats.lock() {
768 stats.total_events_recorded += 1;
769 }
770 }
771
772 fn update_stats_validation_failure(&self) {
773 if let Ok(mut stats) = self.stats.lock() {
774 stats.validation_failures += 1;
775 }
776 }
777}
778
779impl Default for MemoryPassportTracker {
780 fn default() -> Self {
781 Self::new(PassportTrackerConfig::default())
782 }
783}
784
785static GLOBAL_MEMORY_PASSPORT_TRACKER: std::sync::OnceLock<Arc<MemoryPassportTracker>> =
787 std::sync::OnceLock::new();
788
789pub fn get_global_passport_tracker() -> Arc<MemoryPassportTracker> {
795 GLOBAL_MEMORY_PASSPORT_TRACKER
796 .get_or_init(|| Arc::new(MemoryPassportTracker::new(PassportTrackerConfig::default())))
797 .clone()
798}
799
800pub fn initialize_global_passport_tracker(
806 config: PassportTrackerConfig,
807) -> Arc<MemoryPassportTracker> {
808 GLOBAL_MEMORY_PASSPORT_TRACKER
809 .get_or_init(|| Arc::new(MemoryPassportTracker::new(config)))
810 .clone()
811}
812
813#[cfg(test)]
814mod tests {
815 use super::*;
816
817 #[test]
818 fn test_passport_creation() {
819 let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
820 let ptr = 0x1000;
821 let size = 1024;
822
823 let passport_id = tracker
824 .create_passport(ptr, size, "test_context".to_string(), None, None)
825 .expect("Failed to create passport");
826 assert!(!passport_id.is_empty());
827
828 let passport = tracker.get_passport(ptr).expect("Failed to get passport");
829 assert_eq!(passport.allocation_ptr, ptr);
830 assert_eq!(passport.size_bytes, size);
831 assert_eq!(passport.lifecycle_events.len(), 1);
832 assert_eq!(
833 passport.lifecycle_events[0].event_type,
834 PassportEventType::AllocatedInRust
835 );
836 }
837
838 #[test]
839 fn test_handover_to_ffi() {
840 let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
841 let ptr = 0x2000;
842
843 tracker
844 .create_passport(ptr, 512, "rust_context".to_string(), None, None)
845 .expect("Failed to create passport");
846 tracker
847 .record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
848 .expect("Failed to record handover");
849
850 let passport = tracker.get_passport(ptr).expect("Failed to get passport");
851 assert_eq!(passport.lifecycle_events.len(), 2);
852 assert_eq!(
853 passport.lifecycle_events[1].event_type,
854 PassportEventType::HandoverToFfi
855 );
856 }
857
858 #[test]
859 fn test_leak_detection() {
860 let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
861 let ptr = 0x3000;
862
863 tracker
865 .create_passport(ptr, 256, "rust_context".to_string(), None, None)
866 .expect("Failed to create passport");
867 tracker
868 .record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
869 .expect("Failed to record handover");
870
871 let leak_result = tracker.detect_leaks_at_shutdown();
873
874 assert_eq!(leak_result.total_leaks, 1);
875 assert_eq!(leak_result.leaked_passports.len(), 1);
876 assert_eq!(leak_result.leak_details.len(), 1);
877 assert_eq!(leak_result.leak_details[0].memory_address, ptr);
878
879 let passport = tracker.get_passport(ptr).expect("Test operation failed");
881 assert_eq!(
882 passport.status_at_shutdown,
883 PassportStatus::InForeignCustody
884 );
885 }
886
887 #[test]
888 fn test_reclaim_prevents_leak() {
889 let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
890 let ptr = 0x4000;
891
892 tracker
894 .create_passport(ptr, 128, "rust_context".to_string(), None, None)
895 .expect("Failed to create passport");
896 tracker
897 .record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
898 .expect("Failed to record handover");
899 tracker
900 .record_reclaimed_by_rust(ptr, "rust_context".to_string(), "cleanup".to_string())
901 .expect("Failed to record reclaim");
902
903 let leak_result = tracker.detect_leaks_at_shutdown();
905
906 assert_eq!(leak_result.total_leaks, 0);
907
908 let passport = tracker.get_passport(ptr).expect("Test operation failed");
910 assert_eq!(passport.status_at_shutdown, PassportStatus::ReclaimedByRust);
911 }
912
913 #[test]
914 fn test_passport_validation() {
915 let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
916 let ptr = 0x5000;
917
918 tracker
919 .create_passport(ptr, 64, "test_context".to_string(), None, None)
920 .expect("Failed to create passport");
921 tracker
922 .record_handover_to_ffi(ptr, "ffi_context".to_string(), "test_func".to_string())
923 .expect("Failed to record handover");
924
925 let is_valid = tracker
926 .validate_passport(ptr)
927 .expect("Failed to validate passport");
928 assert!(is_valid);
929 }
930}