use tracing::debug;
use crate::analysis::{
heap_scanner::{HeapScanner, ScanResult},
relation_inference::{
clone_detector::{detect_clones, CloneConfig},
container_detector::{detect_containers, ContainerConfig},
pointer_scan::{detect_owner, InferenceRecord},
shared_detector::detect_shared,
slice_detector::detect_slice,
RangeMap, RelationEdge, RelationGraph,
},
unsafe_inference::{MemoryView, OwnedMemoryView, TypeKind, UnsafeInferenceEngine},
};
use crate::snapshot::types::ActiveAllocation;
#[derive(Debug, Clone, Default)]
pub struct GraphBuilderConfig {
pub clone_config: CloneConfig,
pub container_config: ContainerConfig,
}
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();
}
debug!(step = 1, "HeapScanner::scan");
let scan_results = HeapScanner::scan(allocations);
debug!(
step = 1,
scan_results = scan_results.len(),
"HeapScanner completed"
);
debug!(step = 2, "Building scan_map");
let scan_map: std::collections::HashMap<(usize, usize), &ScanResult> = scan_results
.iter()
.map(|scan| ((scan.ptr, scan.size), scan))
.collect();
debug!(step = 2, "scan_map completed");
debug!(step = 3, "Running UTI Engine");
let records: Vec<InferenceRecord> = allocations
.iter()
.enumerate()
.map(|(id, alloc)| {
let scan = scan_map.get(&(alloc.ptr.unwrap_or(0), alloc.size));
let (type_kind, confidence) =
if let Some(memory) = scan.and_then(|s| s.memory.as_deref()) {
let view = MemoryView::new(memory);
let guess = UnsafeInferenceEngine::infer_single(&view, alloc.size);
(guess.kind, guess.confidence)
} else {
(TypeKind::Unknown, 0)
};
InferenceRecord {
id,
ptr: alloc.ptr.unwrap_or(0),
size: alloc.size,
memory: scan
.and_then(|s| s.memory.clone())
.map(OwnedMemoryView::new),
type_kind,
confidence,
call_stack_hash: alloc.call_stack_hash,
alloc_time: alloc.allocated_at,
stack_ptr: alloc.stack_ptr,
}
})
.collect();
debug!(step = 3, records = records.len(), "UTI Engine completed");
debug!(step = 4, "Building RangeMap");
let range_map = RangeMap::new(allocations);
debug!(step = 4, "RangeMap completed");
let mut graph = RelationGraph::new();
debug!(step = 5, "detect_owner");
for record in &records {
let edges = detect_owner(record, &range_map);
graph.add_edges(edges);
}
debug!(
step = 5,
edges = graph.edge_count(),
"detect_owner completed"
);
debug!(step = 6, "detect_slice");
let slice_edges = detect_slice(&records, allocations, &range_map);
graph.add_edges(slice_edges);
debug!(
step = 6,
edges = graph.edge_count(),
"detect_slice completed"
);
debug!(step = 7, "detect_clones");
let clone_edges = detect_clones(&records, &config.clone_config);
graph.add_edges(clone_edges);
debug!(
step = 7,
edges = graph.edge_count(),
"detect_clones completed"
);
debug!(step = 8, "detect_containers");
let container_edges = detect_containers(allocations, Some(config.container_config));
graph.add_edges(container_edges);
debug!(
step = 8,
edges = graph.edge_count(),
"detect_containers completed"
);
debug!(step = 9, "detect_variable_evolution");
let var_evolution_edges = detect_variable_evolution(allocations);
graph.add_edges(var_evolution_edges);
debug!(
step = 9,
edges = graph.edge_count(),
"detect_variable_evolution completed"
);
debug!(step = 10, "detect_shared");
let shared_edges = detect_shared(&records, &graph.edges);
graph.add_edges(shared_edges);
debug!(
step = 10,
edges = graph.edge_count(),
"detect_shared completed"
);
debug!("RelationGraphBuilder all steps completed");
graph
}
}
fn detect_variable_evolution(allocations: &[ActiveAllocation]) -> Vec<RelationEdge> {
use crate::analysis::relation_inference::Relation;
use std::collections::HashMap;
let mut var_groups: HashMap<String, Vec<usize>> = HashMap::new();
for (i, alloc) in allocations.iter().enumerate() {
if let Some(ref var_name) = alloc.var_name {
if !var_name.is_empty() && var_name != "unknown" {
var_groups.entry(var_name.clone()).or_default().push(i);
}
}
}
let mut edges = Vec::new();
for indices in var_groups.values() {
if indices.len() < 2 {
continue;
}
let mut sorted_indices: Vec<(usize, u64)> = indices
.iter()
.map(|&i| (i, allocations[i].allocated_at))
.collect();
sorted_indices.sort_by_key(|&(_, time)| time);
for window in sorted_indices.windows(2) {
let (from_idx, _) = window[0];
let (to_idx, _) = window[1];
edges.push(RelationEdge {
from: from_idx,
to: to_idx,
relation: Relation::Evolution,
});
}
}
edges
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::relation_inference::Relation;
use crate::core::types::TrackKind;
fn make_alloc(ptr: usize, size: usize) -> ActiveAllocation {
ActiveAllocation {
ptr: Some(ptr),
size,
kind: TrackKind::HeapOwner { ptr, size },
allocated_at: 1000,
var_name: None,
type_name: None,
thread_id: 0,
call_stack_hash: None,
module_path: None,
stack_ptr: 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::Owns);
graph.add_edge(1, 2, Relation::Slice);
let nodes = graph.all_nodes();
assert_eq!(nodes, vec![0, 1, 2]);
}
}