use crate::analysis::heap_scanner::HeapScanner;
use crate::analysis::relation_inference::clone_detector::{detect_clones, CloneConfig};
use crate::analysis::relation_inference::pointer_scan::{detect_owner, InferenceRecord};
use crate::analysis::relation_inference::shared_detector::detect_shared;
use crate::analysis::relation_inference::slice_detector::detect_slice;
use crate::analysis::relation_inference::{RangeMap, RelationGraph};
use crate::analysis::unsafe_inference::{
MemoryView, OwnedMemoryView, TypeKind, UnsafeInferenceEngine,
};
use crate::snapshot::types::ActiveAllocation;
#[derive(Debug, Clone, Default)]
pub struct GraphBuilderConfig {
pub clone_config: CloneConfig,
}
pub struct RelationGraphBuilder;
impl RelationGraphBuilder {
pub fn build(
allocations: &[ActiveAllocation],
config: Option<GraphBuilderConfig>,
) -> RelationGraph {
let config = config.unwrap_or_default();
if allocations.is_empty() {
return RelationGraph::new();
}
let scan_results = HeapScanner::scan(allocations);
let records: Vec<InferenceRecord> = scan_results
.into_iter()
.enumerate()
.map(|(id, scan)| {
let (type_kind, confidence) = if let Some(ref memory) = scan.memory {
let view = MemoryView::new(memory);
let guess = UnsafeInferenceEngine::infer_single(&view, scan.size);
(guess.kind, guess.confidence)
} else {
(TypeKind::Unknown, 0)
};
InferenceRecord {
id,
ptr: scan.ptr,
size: scan.size,
memory: scan.memory.map(OwnedMemoryView::new),
type_kind,
confidence,
call_stack_hash: allocations[id].call_stack_hash,
alloc_time: allocations[id].allocated_at,
}
})
.collect();
let range_map = RangeMap::new(allocations);
let mut graph = RelationGraph::new();
for record in &records {
let edges = detect_owner(record, &range_map);
graph.add_edges(edges);
}
let slice_edges = detect_slice(&records, allocations, &range_map);
graph.add_edges(slice_edges);
let clone_edges = detect_clones(&records, &config.clone_config);
graph.add_edges(clone_edges);
let shared_edges = detect_shared(&records, &graph.edges);
graph.add_edges(shared_edges);
graph
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::relation_inference::Relation;
fn make_alloc(ptr: usize, size: usize) -> ActiveAllocation {
ActiveAllocation {
ptr,
size,
allocated_at: 1000,
var_name: None,
type_name: None,
thread_id: 0,
call_stack_hash: None,
}
}
#[test]
fn test_build_empty() {
let graph = RelationGraphBuilder::build(&[], None);
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_build_basic_owner_relationship() {
let buf1 = [0u8; 24];
let buf2 = vec![0u8; 1024];
let ptr1 = buf1.as_ptr() as usize;
let ptr2 = buf2.as_ptr() as usize;
let allocs = vec![make_alloc(ptr1, 24), make_alloc(ptr2, 1024)];
let graph = RelationGraphBuilder::build(&allocs, None);
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_build_with_real_vec_metadata() {
let inner = vec![42u8; 256];
let ptr = inner.as_ptr() as usize;
let len = inner.len();
let cap = inner.capacity();
let mut metadata = [0u8; 24];
metadata[0..8].copy_from_slice(&ptr.to_le_bytes());
metadata[8..16].copy_from_slice(&len.to_le_bytes());
metadata[16..24].copy_from_slice(&cap.to_le_bytes());
let meta_ptr = metadata.as_ptr() as usize;
let inner_ptr = inner.as_ptr() as usize;
let allocs = vec![make_alloc(meta_ptr, 24), make_alloc(inner_ptr, 256)];
let graph = RelationGraphBuilder::build(&allocs, None);
assert!(graph.edge_count() <= 2);
}
#[test]
fn test_build_single_allocation() {
let allocs = vec![make_alloc(0x1000, 64)];
let graph = RelationGraphBuilder::build(&allocs, None);
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_builder_with_default_config() {
let allocs = vec![make_alloc(0x1000, 64)];
let graph = RelationGraphBuilder::build(&allocs, Some(GraphBuilderConfig::default()));
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_graph_node_count() {
let mut graph = RelationGraph::new();
graph.add_edge(0, 1, Relation::Owner);
graph.add_edge(1, 2, Relation::Slice);
let nodes = graph.all_nodes();
assert_eq!(nodes, vec![0, 1, 2]);
}
}