use crate::analysis::unsafe_ffi_tracker::StackFrame;
use crate::capture::types::{TrackingError, TrackingResult};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MemoryPassport {
pub passport_id: String,
pub allocation_ptr: usize,
pub size_bytes: usize,
pub type_name: String,
pub var_name: String,
pub status_at_shutdown: PassportStatus,
pub lifecycle_events: Vec<PassportEvent>,
pub created_at: u64,
pub updated_at: u64,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PassportStatus {
FreedByRust,
HandoverToFfi,
FreedByForeign,
ReclaimedByRust,
InForeignCustody,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PassportEvent {
pub event_type: PassportEventType,
pub timestamp: u64,
pub context: String,
pub call_stack: Vec<StackFrame>,
pub metadata: HashMap<String, String>,
pub sequence_number: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PassportEventType {
AllocatedInRust,
HandoverToFfi,
FreedByForeign,
ReclaimedByRust,
BoundaryAccess,
OwnershipTransfer,
ValidationCheck,
CorruptionDetected,
}
pub struct MemoryPassportTracker {
passports: Arc<Mutex<HashMap<usize, MemoryPassport>>>,
sequence_counter: Arc<Mutex<u32>>,
event_sequence: Arc<AtomicU32>,
config: PassportTrackerConfig,
stats: Arc<Mutex<PassportTrackerStats>>,
}
#[derive(Debug, Clone)]
pub struct PassportTrackerConfig {
pub detailed_logging: bool,
pub max_events_per_passport: usize,
pub enable_leak_detection: bool,
pub enable_validation: bool,
pub max_passports: usize,
pub track_rust_internal_stack: bool,
pub user_code_prefixes: Vec<String>,
}
impl Default for PassportTrackerConfig {
fn default() -> Self {
Self {
detailed_logging: true,
max_events_per_passport: 100,
enable_leak_detection: true,
enable_validation: true,
max_passports: 10000,
track_rust_internal_stack: false,
user_code_prefixes: vec![
"src/".to_string(),
"examples/".to_string(),
"tests/".to_string(),
"benches/".to_string(),
],
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PassportTrackerStats {
pub total_passports_created: usize,
pub active_passports: usize,
pub passports_by_status: HashMap<String, usize>,
pub total_events_recorded: usize,
pub leaks_detected: usize,
pub validation_failures: usize,
pub tracker_start_time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeakDetectionResult {
pub leaked_passports: Vec<String>,
pub total_leaks: usize,
pub leak_details: Vec<LeakDetail>,
pub detected_at: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeakDetail {
pub passport_id: String,
pub memory_address: usize,
pub size_bytes: usize,
pub last_context: String,
pub time_since_last_event: u64,
pub lifecycle_summary: String,
}
impl MemoryPassportTracker {
pub fn new(config: PassportTrackerConfig) -> Self {
tracing::debug!("Initializing Memory Passport Tracker");
tracing::debug!(" Detailed logging: {}", config.detailed_logging);
tracing::debug!(
" Max events per passport: {}",
config.max_events_per_passport
);
tracing::debug!(" Leak detection: {}", config.enable_leak_detection);
tracing::debug!(" Validation: {}", config.enable_validation);
Self {
passports: Arc::new(Mutex::new(HashMap::new())),
sequence_counter: Arc::new(Mutex::new(0)),
event_sequence: Arc::new(AtomicU32::new(0)),
config,
stats: Arc::new(Mutex::new(PassportTrackerStats {
tracker_start_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
..Default::default()
})),
}
}
pub fn create_passport_simple(
&self,
allocation_ptr: usize,
size_bytes: usize,
initial_context: String,
) -> TrackingResult<String> {
self.create_passport(allocation_ptr, size_bytes, initial_context, None, None)
}
pub fn create_passport(
&self,
allocation_ptr: usize,
size_bytes: usize,
initial_context: String,
type_name: Option<String>,
var_name: Option<String>,
) -> TrackingResult<String> {
let passport_id = self.generate_passport_id(allocation_ptr)?;
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let initial_event = PassportEvent {
event_type: PassportEventType::AllocatedInRust,
timestamp: current_time,
context: initial_context,
call_stack: self.capture_call_stack()?,
metadata: HashMap::new(),
sequence_number: self.get_next_event_sequence(),
};
let passport = MemoryPassport {
passport_id: passport_id.clone(),
allocation_ptr,
size_bytes,
type_name: type_name.unwrap_or_else(|| "-".to_string()),
var_name: var_name.unwrap_or_else(|| "-".to_string()),
status_at_shutdown: PassportStatus::Unknown,
lifecycle_events: vec![initial_event],
created_at: current_time,
updated_at: current_time,
metadata: HashMap::new(),
};
if let Ok(mut passports) = self.passports.lock() {
if passports.len() >= self.config.max_passports {
return Err(TrackingError::ResourceExhausted(
"Maximum passport limit reached".to_string(),
));
}
passports.insert(allocation_ptr, passport);
} else {
return Err(TrackingError::LockContention(
"Failed to lock passports".to_string(),
));
}
self.update_stats_passport_created();
if self.config.detailed_logging {
tracing::debug!(
"Created passport: {} for 0x{:x} ({} bytes)",
passport_id,
allocation_ptr,
size_bytes
);
}
Ok(passport_id)
}
pub fn create_passport_with_inference(
&self,
allocation_ptr: usize,
size_bytes: usize,
memory: Option<&[u8]>,
initial_context: String,
var_name: Option<String>,
) -> TrackingResult<String> {
let type_name = memory
.map(|m| {
let guess =
crate::analysis::unsafe_inference::UnsafeInferenceEngine::infer_from_bytes(
m, size_bytes,
);
guess.display_with_confidence()
})
.unwrap_or_else(|| "-".to_string());
self.create_passport(
allocation_ptr,
size_bytes,
initial_context,
Some(type_name),
var_name,
)
}
pub fn record_handover_to_ffi(
&self,
allocation_ptr: usize,
ffi_context: String,
function_name: String,
) -> TrackingResult<()> {
let mut metadata = HashMap::new();
metadata.insert("ffi_function".to_string(), function_name);
self.record_passport_event(
allocation_ptr,
PassportEventType::HandoverToFfi,
ffi_context,
metadata,
)
}
pub fn record_freed_by_foreign(
&self,
allocation_ptr: usize,
foreign_context: String,
free_function: String,
) -> TrackingResult<()> {
let mut metadata = HashMap::new();
metadata.insert("free_function".to_string(), free_function);
self.record_passport_event(
allocation_ptr,
PassportEventType::FreedByForeign,
foreign_context,
metadata,
)
}
pub fn record_reclaimed_by_rust(
&self,
allocation_ptr: usize,
rust_context: String,
reclaim_reason: String,
) -> TrackingResult<()> {
let mut metadata = HashMap::new();
metadata.insert("reclaim_reason".to_string(), reclaim_reason);
self.record_passport_event(
allocation_ptr,
PassportEventType::ReclaimedByRust,
rust_context,
metadata,
)
}
pub fn record_passport_event(
&self,
allocation_ptr: usize,
event_type: PassportEventType,
context: String,
metadata: HashMap<String, String>,
) -> TrackingResult<()> {
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let event = PassportEvent {
event_type: event_type.clone(),
timestamp: current_time,
context: context.clone(),
call_stack: self.capture_call_stack()?,
metadata,
sequence_number: self.get_next_event_sequence(),
};
if let Ok(mut passports) = self.passports.lock() {
if let Some(passport) = passports.get_mut(&allocation_ptr) {
if passport.lifecycle_events.len() >= self.config.max_events_per_passport {
passport.lifecycle_events.drain(0..1); }
passport.lifecycle_events.push(event);
passport.updated_at = current_time;
self.update_stats_event_recorded();
if self.config.detailed_logging {
tracing::debug!(
"Recorded {:?} event for passport 0x{:x} in context: {}",
event_type,
allocation_ptr,
context
);
}
} else {
return Err(TrackingError::InvalidPointer(format!(
"No passport found for 0x{allocation_ptr:x}"
)));
}
} else {
return Err(TrackingError::LockContention(
"Failed to lock passports".to_string(),
));
}
Ok(())
}
pub fn detect_leaks_at_shutdown(&self) -> LeakDetectionResult {
if !self.config.enable_leak_detection {
return LeakDetectionResult {
leaked_passports: Vec::new(),
total_leaks: 0,
leak_details: Vec::new(),
detected_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
};
}
let mut leaked_passports = Vec::new();
let mut leak_details = Vec::new();
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if let Ok(mut passports) = self.passports.lock() {
for (ptr, passport) in passports.iter_mut() {
let final_status = self.determine_final_status(&passport.lifecycle_events);
passport.status_at_shutdown = final_status.clone();
if final_status == PassportStatus::InForeignCustody {
leaked_passports.push(passport.passport_id.clone());
let last_event = passport.lifecycle_events.last();
let last_context = last_event
.map(|e| e.context.clone())
.unwrap_or_else(|| "unknown".to_string());
let time_since_last = last_event
.map(|e| current_time.saturating_sub(e.timestamp))
.unwrap_or(0);
let lifecycle_summary =
self.create_lifecycle_summary(&passport.lifecycle_events);
leak_details.push(LeakDetail {
passport_id: passport.passport_id.clone(),
memory_address: *ptr,
size_bytes: passport.size_bytes,
last_context,
time_since_last_event: time_since_last,
lifecycle_summary,
});
tracing::warn!("🚨 MEMORY LEAK DETECTED: Passport {} (0x{:x}, {} bytes) in foreign custody",
passport.passport_id, ptr, passport.size_bytes);
}
}
if let Ok(mut stats) = self.stats.lock() {
stats.leaks_detected = leaked_passports.len();
stats.passports_by_status.clear();
for passport in passports.values() {
let status_key = format!("{:?}", passport.status_at_shutdown);
*stats.passports_by_status.entry(status_key).or_insert(0) += 1;
}
}
}
let total_leaks = leaked_passports.len();
tracing::debug!(
"Leak detection complete: {} leaks detected out of {} passports",
total_leaks,
self.get_passport_count()
);
LeakDetectionResult {
leaked_passports,
total_leaks,
leak_details,
detected_at: current_time,
}
}
pub fn get_passport(&self, allocation_ptr: usize) -> Option<MemoryPassport> {
self.passports.lock().ok()?.get(&allocation_ptr).cloned()
}
pub fn get_all_passports(&self) -> HashMap<usize, MemoryPassport> {
self.passports
.lock()
.unwrap_or_else(|e| {
tracing::warn!(
"Mutex poisoned in get_all_passports, recovering data: {}",
e
);
e.into_inner()
})
.clone()
}
pub fn get_passports_by_status(&self, status: PassportStatus) -> Vec<MemoryPassport> {
if let Ok(passports) = self.passports.lock() {
passports
.values()
.filter(|p| p.status_at_shutdown == status)
.cloned()
.collect()
} else {
Vec::new()
}
}
pub fn get_stats(&self) -> PassportTrackerStats {
self.stats
.lock()
.unwrap_or_else(|e| {
tracing::warn!("Mutex poisoned in get_stats, recovering data: {}", e);
e.into_inner()
})
.clone()
}
pub fn validate_passport(&self, allocation_ptr: usize) -> TrackingResult<bool> {
if !self.config.enable_validation {
return Ok(true);
}
if let Ok(passports) = self.passports.lock() {
if let Some(passport) = passports.get(&allocation_ptr) {
let mut last_sequence = 0;
for event in &passport.lifecycle_events {
if event.sequence_number < last_sequence {
self.update_stats_validation_failure();
return Ok(false);
}
last_sequence = event.sequence_number;
}
let mut last_timestamp = 0;
for event in &passport.lifecycle_events {
if event.timestamp < last_timestamp {
self.update_stats_validation_failure();
return Ok(false);
}
last_timestamp = event.timestamp;
}
Ok(true)
} else {
Err(TrackingError::InvalidPointer(format!(
"No passport found for 0x{allocation_ptr:x}",
)))
}
} else {
Err(TrackingError::LockContention(
"Failed to lock passports".to_string(),
))
}
}
pub fn clear_all_passports(&self) {
if let Ok(mut passports) = self.passports.lock() {
passports.clear();
}
if let Ok(mut stats) = self.stats.lock() {
*stats = PassportTrackerStats {
tracker_start_time: stats.tracker_start_time,
..Default::default()
};
}
tracing::debug!("Cleared all passports");
}
fn generate_passport_id(&self, allocation_ptr: usize) -> TrackingResult<String> {
let sequence = if let Ok(mut counter) = self.sequence_counter.lock() {
*counter += 1;
*counter
} else {
return Err(TrackingError::LockContention(
"Failed to lock sequence counter".to_string(),
));
};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
Ok(format!(
"passport_{:x}_{:08x}_{}",
allocation_ptr,
sequence,
timestamp % 1000000
))
}
fn get_next_event_sequence(&self) -> u32 {
self.event_sequence.fetch_add(1, Ordering::SeqCst)
}
fn capture_call_stack(&self) -> TrackingResult<Vec<StackFrame>> {
#[cfg(feature = "backtrace")]
{
let backtrace = backtrace::Backtrace::new();
let mut frames = Vec::new();
let skip_frames = 2;
let max_depth = 32;
for (i, frame) in backtrace.frames().iter().enumerate() {
if i < skip_frames {
continue;
}
if let Some(symbol) = frame.symbols().first() {
frames.push(StackFrame {
function_name: symbol
.name()
.map(|n| format!("{:?}", n))
.unwrap_or_else(|| "unknown".to_string()),
file_name: symbol.filename().map(|p| p.display().to_string()),
line_number: symbol.lineno(),
is_unsafe: false,
});
}
if frames.len() >= max_depth {
break;
}
}
Ok(frames)
}
#[cfg(not(feature = "backtrace"))]
{
Ok(Vec::new())
}
}
fn determine_final_status(&self, events: &[PassportEvent]) -> PassportStatus {
let mut has_handover = false;
let mut has_reclaim = false;
let mut has_foreign_free = false;
let mut has_corruption = false;
for event in events {
match event.event_type {
PassportEventType::HandoverToFfi => has_handover = true,
PassportEventType::ReclaimedByRust => {
has_reclaim = true;
has_handover = false;
}
PassportEventType::FreedByForeign => {
has_foreign_free = true;
has_handover = false;
}
PassportEventType::CorruptionDetected => has_corruption = true,
_ => {}
}
}
if has_corruption {
PassportStatus::Unknown
} else if has_handover && !has_reclaim && !has_foreign_free {
PassportStatus::InForeignCustody
} else if has_foreign_free {
PassportStatus::FreedByForeign
} else if has_reclaim {
PassportStatus::ReclaimedByRust
} else if has_handover {
PassportStatus::HandoverToFfi
} else {
PassportStatus::FreedByRust
}
}
fn create_lifecycle_summary(&self, events: &[PassportEvent]) -> String {
let event_types: Vec<String> = events
.iter()
.map(|e| format!("{:?}", e.event_type))
.collect();
if event_types.is_empty() {
"No events recorded".to_string()
} else {
format!("Events: {}", event_types.join(" -> "))
}
}
fn get_passport_count(&self) -> usize {
self.passports.lock().map(|p| p.len()).unwrap_or(0)
}
fn update_stats_passport_created(&self) {
if let Ok(mut stats) = self.stats.lock() {
stats.total_passports_created += 1;
stats.active_passports = self.get_passport_count();
}
}
fn update_stats_event_recorded(&self) {
if let Ok(mut stats) = self.stats.lock() {
stats.total_events_recorded += 1;
}
}
fn update_stats_validation_failure(&self) {
if let Ok(mut stats) = self.stats.lock() {
stats.validation_failures += 1;
}
}
}
impl Default for MemoryPassportTracker {
fn default() -> Self {
Self::new(PassportTrackerConfig::default())
}
}
static GLOBAL_MEMORY_PASSPORT_TRACKER: std::sync::OnceLock<Arc<MemoryPassportTracker>> =
std::sync::OnceLock::new();
pub fn get_global_passport_tracker() -> Arc<MemoryPassportTracker> {
GLOBAL_MEMORY_PASSPORT_TRACKER
.get_or_init(|| Arc::new(MemoryPassportTracker::new(PassportTrackerConfig::default())))
.clone()
}
pub fn initialize_global_passport_tracker(
config: PassportTrackerConfig,
) -> Arc<MemoryPassportTracker> {
GLOBAL_MEMORY_PASSPORT_TRACKER
.get_or_init(|| Arc::new(MemoryPassportTracker::new(config)))
.clone()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_passport_creation() {
let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
let ptr = 0x1000;
let size = 1024;
let passport_id = tracker
.create_passport(ptr, size, "test_context".to_string(), None, None)
.expect("Failed to create passport");
assert!(!passport_id.is_empty());
let passport = tracker.get_passport(ptr).expect("Failed to get passport");
assert_eq!(passport.allocation_ptr, ptr);
assert_eq!(passport.size_bytes, size);
assert_eq!(passport.lifecycle_events.len(), 1);
assert_eq!(
passport.lifecycle_events[0].event_type,
PassportEventType::AllocatedInRust
);
}
#[test]
fn test_handover_to_ffi() {
let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
let ptr = 0x2000;
tracker
.create_passport(ptr, 512, "rust_context".to_string(), None, None)
.expect("Failed to create passport");
tracker
.record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
.expect("Failed to record handover");
let passport = tracker.get_passport(ptr).expect("Failed to get passport");
assert_eq!(passport.lifecycle_events.len(), 2);
assert_eq!(
passport.lifecycle_events[1].event_type,
PassportEventType::HandoverToFfi
);
}
#[test]
fn test_leak_detection() {
let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
let ptr = 0x3000;
tracker
.create_passport(ptr, 256, "rust_context".to_string(), None, None)
.expect("Failed to create passport");
tracker
.record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
.expect("Failed to record handover");
let leak_result = tracker.detect_leaks_at_shutdown();
assert_eq!(leak_result.total_leaks, 1);
assert_eq!(leak_result.leaked_passports.len(), 1);
assert_eq!(leak_result.leak_details.len(), 1);
assert_eq!(leak_result.leak_details[0].memory_address, ptr);
let passport = tracker.get_passport(ptr).expect("Test operation failed");
assert_eq!(
passport.status_at_shutdown,
PassportStatus::InForeignCustody
);
}
#[test]
fn test_reclaim_prevents_leak() {
let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
let ptr = 0x4000;
tracker
.create_passport(ptr, 128, "rust_context".to_string(), None, None)
.expect("Failed to create passport");
tracker
.record_handover_to_ffi(ptr, "ffi_context".to_string(), "malloc".to_string())
.expect("Failed to record handover");
tracker
.record_reclaimed_by_rust(ptr, "rust_context".to_string(), "cleanup".to_string())
.expect("Failed to record reclaim");
let leak_result = tracker.detect_leaks_at_shutdown();
assert_eq!(leak_result.total_leaks, 0);
let passport = tracker.get_passport(ptr).expect("Test operation failed");
assert_eq!(passport.status_at_shutdown, PassportStatus::ReclaimedByRust);
}
#[test]
fn test_passport_validation() {
let tracker = MemoryPassportTracker::new(PassportTrackerConfig::default());
let ptr = 0x5000;
tracker
.create_passport(ptr, 64, "test_context".to_string(), None, None)
.expect("Failed to create passport");
tracker
.record_handover_to_ffi(ptr, "ffi_context".to_string(), "test_func".to_string())
.expect("Failed to record handover");
let is_valid = tracker
.validate_passport(ptr)
.expect("Failed to validate passport");
assert!(is_valid);
}
}