use crate::analysis::relation_inference::{Relation, RelationGraph};
use crate::analyzer::report::{CycleInfo, CycleReport};
use crate::view::MemoryView;
use std::collections::HashSet;
use tracing::{debug, info, warn};
#[derive(Debug, Clone)]
pub struct RelationshipEdge {
pub from_ptr: usize,
pub to_ptr: usize,
pub from_var_name: Option<String>,
pub to_var_name: Option<String>,
pub from_type_name: Option<String>,
pub to_type_name: Option<String>,
pub relation: Relation,
pub is_container_source: bool,
pub is_container_target: bool,
}
pub struct GraphAnalysis {
view: MemoryView,
relation_graph: Option<RelationGraph>,
}
impl GraphAnalysis {
pub fn from_view(view: &MemoryView) -> Self {
debug!("Creating GraphAnalysis with {} allocations", view.len());
Self {
view: view.clone(),
relation_graph: None,
}
}
fn relation_graph(&mut self) -> &RelationGraph {
if self.relation_graph.is_none() {
info!("Building relation graph from view");
self.relation_graph = Some(RelationGraph::from_view(&self.view));
}
self.relation_graph.as_ref().unwrap_or_else(|| {
unreachable!("RelationGraph should be initialized after lazy initialization")
})
}
pub fn cycles(&mut self) -> CycleReport {
let allocations = self.view.allocations();
if allocations.is_empty() {
debug!("Cycle detection: no allocations, returning empty report");
return CycleReport::empty();
}
let relation_graph = self.relation_graph();
let cycle_edges = relation_graph.detect_cycles();
if cycle_edges.is_empty() {
debug!("Cycle detection: no cycles found");
return CycleReport::empty();
}
info!("Cycle detection: found {} cycles", cycle_edges.len());
let cycles: Vec<CycleInfo> = cycle_edges
.iter()
.map(|(from, to, _)| CycleInfo {
nodes: vec![*from as u64, *to as u64],
})
.collect();
CycleReport {
cycle_count: cycles.len(),
cycles,
}
}
pub fn ownership_stats(&self) -> OwnershipStats {
let allocations = self.view.allocations();
OwnershipStats {
total_objects: allocations.len(),
total_bytes: allocations.iter().map(|a| a.size).sum(),
unique_types: allocations
.iter()
.filter_map(|a| a.type_name.as_ref())
.collect::<HashSet<_>>()
.len(),
}
}
pub fn relationship_stats(&mut self) -> RelationshipStats {
let relation_graph = self.relation_graph();
let edges = &relation_graph.edges;
let mut ownership_edges = 0usize;
let mut contains_edges = 0usize;
let mut shares_edges = 0usize;
let mut slice_edges = 0usize;
let mut clone_edges = 0usize;
for edge in edges {
match edge.relation {
Relation::Owns => ownership_edges += 1,
Relation::Contains => contains_edges += 1,
Relation::Shares => shares_edges += 1,
Relation::Slice => slice_edges += 1,
Relation::Clone => clone_edges += 1,
Relation::Evolution => {}
Relation::ArcClone => clone_edges += 1,
Relation::RcClone => clone_edges += 1,
Relation::ImmutableBorrow => slice_edges += 1,
Relation::MutableBorrow => ownership_edges += 1,
}
}
RelationshipStats {
ownership_edges,
contains_edges,
shares_edges,
slice_edges,
clone_edges,
}
}
pub fn relationships(&mut self) -> Vec<RelationshipEdge> {
let allocations: Vec<crate::snapshot::types::ActiveAllocation> = self
.view
.allocations()
.iter()
.map(|a| (*a).clone())
.collect();
let relation_graph = self.relation_graph();
let edges = &relation_graph.edges;
debug!("Building relationships from {} edges", edges.len());
let mut relationships: Vec<RelationshipEdge> = edges
.iter()
.filter_map(|edge| {
let from_alloc = allocations.get(edge.from);
let to_alloc = allocations.get(edge.to);
let from_ptr = from_alloc.and_then(|a| a.ptr).unwrap_or(edge.from);
let to_ptr = to_alloc.and_then(|a| a.ptr).unwrap_or(edge.to);
if from_ptr == 0 && to_ptr == 0 {
return None;
}
Some(RelationshipEdge {
from_ptr,
to_ptr,
from_var_name: from_alloc.and_then(|a| a.var_name.clone()),
to_var_name: to_alloc.and_then(|a| a.var_name.clone()),
from_type_name: from_alloc.and_then(|a| a.type_name.clone()),
to_type_name: to_alloc.and_then(|a| a.type_name.clone()),
relation: edge.relation.clone(),
is_container_source: from_ptr == 0,
is_container_target: to_ptr == 0,
})
})
.collect();
if relationships.len() > 500 {
warn!(
"Relationship count ({}) exceeds limit, truncating to 500",
relationships.len()
);
relationships.truncate(500);
}
let borrow_analyzer = crate::analysis::borrow_analysis::get_global_borrow_analyzer();
let borrow_history = borrow_analyzer.get_borrow_history();
for event in &borrow_history {
let borrow_relation = match event.borrow_info.borrow_type {
crate::analysis::borrow_analysis::BorrowType::Immutable => {
crate::analysis::relation_inference::Relation::ImmutableBorrow
}
crate::analysis::borrow_analysis::BorrowType::Mutable => {
crate::analysis::relation_inference::Relation::MutableBorrow
}
crate::analysis::borrow_analysis::BorrowType::Shared => {
crate::analysis::relation_inference::Relation::Shares
}
crate::analysis::borrow_analysis::BorrowType::Weak => {
crate::analysis::relation_inference::Relation::Contains
}
};
relationships.push(RelationshipEdge {
from_ptr: event.borrow_info.ptr,
to_ptr: event.borrow_info.ptr,
from_var_name: Some(event.borrow_info.var_name.clone()),
to_var_name: Some(format!("borrow_{:?}", event.borrow_info.id)),
from_type_name: Some(format!("{:?}", event.borrow_info.borrow_type)),
to_type_name: Some(format!("{:?}", event.borrow_info.borrow_type)),
relation: borrow_relation,
is_container_source: false,
is_container_target: false,
});
}
debug!("Returning {} relationships", relationships.len());
relationships
}
}
#[derive(Debug, Clone)]
pub struct OwnershipStats {
pub total_objects: usize,
pub total_bytes: usize,
pub unique_types: usize,
}
#[derive(Debug, Clone, Default)]
pub struct RelationshipStats {
pub ownership_edges: usize,
pub contains_edges: usize,
pub shares_edges: usize,
pub slice_edges: usize,
pub clone_edges: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event_store::MemoryEvent;
#[test]
fn test_graph_analysis() {
let events = vec![MemoryEvent::allocate(0x1000, 64, 1)];
let view = MemoryView::from_events(events);
let analysis = GraphAnalysis::from_view(&view);
let stats = analysis.ownership_stats();
assert_eq!(stats.total_objects, 1);
}
#[test]
fn test_cycle_detection_empty() {
let events: Vec<MemoryEvent> = vec![];
let view = MemoryView::from_events(events);
let mut analysis = GraphAnalysis::from_view(&view);
let cycles = analysis.cycles();
assert_eq!(cycles.cycle_count, 0);
}
#[test]
fn test_relationship_stats() {
use crate::core::types::TrackKind;
use crate::snapshot::types::ActiveAllocation;
let buf1 = vec![0u8; 64];
let buf2 = vec![0u8; 128];
let ptr1 = buf1.as_ptr() as usize;
let ptr2 = buf2.as_ptr() as usize;
let allocs = vec![
ActiveAllocation {
ptr: Some(ptr1),
size: 64,
kind: TrackKind::HeapOwner {
ptr: ptr1,
size: 64,
},
allocated_at: 1000,
var_name: None,
type_name: None,
thread_id: 1,
call_stack_hash: None,
module_path: None,
stack_ptr: None,
},
ActiveAllocation {
ptr: Some(ptr2),
size: 128,
kind: TrackKind::HeapOwner {
ptr: ptr2,
size: 128,
},
allocated_at: 1001,
var_name: None,
type_name: None,
thread_id: 1,
call_stack_hash: None,
module_path: None,
stack_ptr: None,
},
];
let snapshot = crate::snapshot::MemorySnapshot::new();
let mut snapshot = snapshot;
for alloc in allocs {
snapshot
.active_allocations
.insert(alloc.ptr.unwrap(), alloc);
}
let view = MemoryView::new(snapshot, vec![]);
let mut analysis = GraphAnalysis::from_view(&view);
let stats = analysis.relationship_stats();
let _ = stats.ownership_edges;
drop(buf1);
drop(buf2);
}
}