mod clone_detector;
mod container_detector;
mod graph_builder;
mod pointer_scan;
mod range_map;
mod shared_detector;
mod slice_detector;
pub use clone_detector::{detect_clones, CloneConfig};
pub use container_detector::{detect_containers, ContainerConfig};
pub use graph_builder::{GraphBuilderConfig, RelationGraphBuilder};
pub use pointer_scan::{detect_owner, InferenceRecord};
pub use range_map::RangeMap;
pub use shared_detector::detect_shared;
pub use slice_detector::detect_slice;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Relation {
Owns,
Contains,
Shares,
Slice,
Clone,
Evolution,
ArcClone,
RcClone,
ImmutableBorrow,
MutableBorrow,
}
impl std::fmt::Display for Relation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Relation::Owns => write!(f, "Owns"),
Relation::Contains => write!(f, "Contains"),
Relation::Shares => write!(f, "Shares"),
Relation::Slice => write!(f, "Slice"),
Relation::Clone => write!(f, "Clone"),
Relation::Evolution => write!(f, "Evolution"),
Relation::ArcClone => write!(f, "ArcClone"),
Relation::RcClone => write!(f, "RcClone"),
Relation::ImmutableBorrow => write!(f, "ImmutableBorrow"),
Relation::MutableBorrow => write!(f, "MutableBorrow"),
}
}
}
#[derive(Debug, Clone)]
pub struct RelationEdge {
pub from: usize,
pub to: usize,
pub relation: Relation,
}
#[derive(Debug, Default, Clone)]
pub struct RelationGraph {
pub edges: Vec<RelationEdge>,
}
impl RelationGraph {
pub fn new() -> Self {
Self::default()
}
pub fn from_view(view: &crate::view::MemoryView) -> Self {
use crate::snapshot::types::ActiveAllocation;
let allocations: Vec<ActiveAllocation> =
view.allocations().iter().map(|a| (*a).clone()).collect();
RelationGraphBuilder::build(&allocations, None)
}
pub fn add_edge(&mut self, from: usize, to: usize, relation: Relation) {
if from == to {
return;
}
let exists = self
.edges
.iter()
.any(|e| e.from == from && e.to == to && e.relation == relation);
if !exists {
self.edges.push(RelationEdge { from, to, relation });
}
}
pub fn add_edges(&mut self, edges: Vec<RelationEdge>) {
for edge in edges {
self.add_edge(edge.from, edge.to, edge.relation);
}
}
pub fn get_inbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
self.edges.iter().filter(|e| e.to == node).collect()
}
pub fn get_outbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
self.edges.iter().filter(|e| e.from == node).collect()
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
pub fn all_nodes(&self) -> Vec<usize> {
let mut nodes: Vec<usize> = self.edges.iter().flat_map(|e| [e.from, e.to]).collect();
nodes.sort();
nodes.dedup();
nodes
}
pub fn detect_cycles(&self) -> Vec<(usize, usize, Relation)> {
if self.edges.is_empty() {
return Vec::new();
}
use crate::analysis::relationship_cycle_detector::detect_cycles_direct;
let relationships: Vec<(usize, usize)> =
self.edges.iter().map(|e| (e.from, e.to)).collect();
let cycle_indices = detect_cycles_direct(&relationships);
cycle_indices
.into_iter()
.filter_map(|(from_idx, to_idx)| {
self.edges.iter().find_map(|e| {
if e.from == from_idx && e.to == to_idx {
Some((e.from, e.to, e.relation.clone()))
} else {
None
}
})
})
.collect()
}
}
mod tests {
#[allow(unused_imports)]
use crate::{Relation, RelationEdge, RelationGraph};
#[test]
fn test_graph_add_edge() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owns);
assert_eq!(graph.edge_count(), 1);
assert_eq!(graph.edges[0].from, 0);
assert_eq!(graph.edges[0].to, 1);
assert_eq!(graph.edges[0].relation, Relation::Owns);
}
#[test]
fn test_graph_no_self_edges() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 0, Relation::Owns);
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_graph_no_duplicate_edges() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owns);
graph.add_edge(0, 1, Relation::Owns);
assert_eq!(graph.edge_count(), 1);
}
#[test]
fn test_graph_different_relations_allowed() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owns);
graph.add_edge(0, 1, Relation::Clone);
assert_eq!(graph.edge_count(), 2);
}
#[test]
fn test_graph_inbound_outbound_edges() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 2, Relation::Owns);
graph.add_edge(1, 2, Relation::Owns);
graph.add_edge(2, 3, Relation::Slice);
let inbound_to_2 = graph.get_inbound_edges(2);
assert_eq!(inbound_to_2.len(), 2);
let outbound_from_2 = graph.get_outbound_edges(2);
assert_eq!(outbound_from_2.len(), 1);
}
#[test]
fn test_graph_all_nodes() {
let mut graph = RelationGraph::new();
graph.add_edge(3, 1, Relation::Owns);
graph.add_edge(1, 2, Relation::Slice);
let nodes = graph.all_nodes();
assert_eq!(nodes, vec![1, 2, 3]);
}
#[test]
fn test_graph_add_edges_batch() {
let mut graph = RelationGraph::new();
graph.add_edges(vec![
RelationEdge {
from: 0,
to: 1,
relation: Relation::Owns,
},
RelationEdge {
from: 1,
to: 2,
relation: Relation::Slice,
},
]);
assert_eq!(graph.edge_count(), 2);
}
#[test]
fn test_graph_detect_cycles_none() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owns);
graph.add_edge(1, 2, Relation::Slice);
let cycles = graph.detect_cycles();
assert!(cycles.is_empty());
}
#[test]
fn test_graph_detect_cycles_simple() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owns);
graph.add_edge(1, 0, Relation::Owns);
let cycles = graph.detect_cycles();
assert!(!cycles.is_empty());
let edge_pairs: Vec<_> = cycles.iter().map(|(f, t, _)| (*f, *t)).collect();
assert!(edge_pairs.contains(&(0, 1)) || edge_pairs.contains(&(1, 0)));
}
#[test]
fn test_graph_detect_cycles_longer() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owns);
graph.add_edge(1, 2, Relation::Slice);
graph.add_edge(2, 0, Relation::Clone);
let cycles = graph.detect_cycles();
assert!(!cycles.is_empty());
let has_back_edge = cycles.iter().any(|(f, t, _)| *f == 2 && *t == 0);
assert!(has_back_edge, "cycle should contain back-edge (2, 0)");
}
#[test]
fn test_graph_detect_cycles_empty() {
let graph = RelationGraph::new();
let cycles = graph.detect_cycles();
assert!(cycles.is_empty());
}
#[test]
fn test_graph_detect_cycles_self_loop_blocked() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 0, Relation::Owns);
assert_eq!(graph.edge_count(), 0);
let cycles = graph.detect_cycles();
assert!(cycles.is_empty());
}
#[test]
fn test_graph_detect_cycles_diamond() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owns);
graph.add_edge(0, 2, Relation::Owns);
graph.add_edge(1, 3, Relation::Slice);
graph.add_edge(2, 3, Relation::Slice);
graph.add_edge(3, 0, Relation::Clone);
let cycles = graph.detect_cycles();
assert!(!cycles.is_empty());
}
}