memscope_rs/analysis/relation_inference/
graph_builder.rs1use tracing::debug;
7
8use crate::analysis::{
9 heap_scanner::{HeapScanner, ScanResult},
10 relation_inference::{
11 clone_detector::{detect_clones, CloneConfig},
12 container_detector::{detect_containers, ContainerConfig},
13 pointer_scan::{detect_owner, InferenceRecord},
14 shared_detector::detect_shared,
15 slice_detector::detect_slice,
16 RangeMap, RelationEdge, RelationGraph,
17 },
18 unsafe_inference::{MemoryView, OwnedMemoryView, TypeKind, UnsafeInferenceEngine},
19};
20
21use crate::snapshot::types::ActiveAllocation;
22
23#[derive(Debug, Clone, Default)]
55pub struct GraphBuilderConfig {
56 pub clone_config: CloneConfig,
68
69 pub container_config: ContainerConfig,
79}
80
81pub struct RelationGraphBuilder;
101
102impl RelationGraphBuilder {
103 pub fn build(
114 allocations: &[ActiveAllocation],
115 config: Option<GraphBuilderConfig>,
116 ) -> RelationGraph {
117 let config = config.unwrap_or_default();
118
119 if allocations.is_empty() {
120 return RelationGraph::new();
121 }
122
123 debug!(step = 1, "HeapScanner::scan");
125 let scan_results = HeapScanner::scan(allocations);
126 debug!(
127 step = 1,
128 scan_results = scan_results.len(),
129 "HeapScanner completed"
130 );
131
132 debug!(step = 2, "Building scan_map");
134 let scan_map: std::collections::HashMap<(usize, usize), &ScanResult> = scan_results
135 .iter()
136 .map(|scan| ((scan.ptr, scan.size), scan))
137 .collect();
138 debug!(step = 2, "scan_map completed");
139
140 debug!(step = 3, "Running UTI Engine");
143 let records: Vec<InferenceRecord> = allocations
144 .iter()
145 .enumerate()
146 .map(|(id, alloc)| {
147 let scan = scan_map.get(&(alloc.ptr.unwrap_or(0), alloc.size));
148
149 let (type_kind, confidence) =
150 if let Some(memory) = scan.and_then(|s| s.memory.as_deref()) {
151 let view = MemoryView::new(memory);
152 let guess = UnsafeInferenceEngine::infer_single(&view, alloc.size);
153 (guess.kind, guess.confidence)
154 } else {
155 (TypeKind::Unknown, 0)
156 };
157
158 InferenceRecord {
159 id,
160 ptr: alloc.ptr.unwrap_or(0),
161 size: alloc.size,
162 memory: scan
163 .and_then(|s| s.memory.clone())
164 .map(OwnedMemoryView::new),
165 type_kind,
166 confidence,
167 call_stack_hash: alloc.call_stack_hash,
168 alloc_time: alloc.allocated_at,
169 stack_ptr: alloc.stack_ptr,
170 }
171 })
172 .collect();
173 debug!(step = 3, records = records.len(), "UTI Engine completed");
174
175 debug!(step = 4, "Building RangeMap");
177 let range_map = RangeMap::new(allocations);
178 debug!(step = 4, "RangeMap completed");
179
180 let mut graph = RelationGraph::new();
182
183 debug!(step = 5, "detect_owner");
185 for record in &records {
186 let edges = detect_owner(record, &range_map);
187 graph.add_edges(edges);
188 }
189 debug!(
190 step = 5,
191 edges = graph.edge_count(),
192 "detect_owner completed"
193 );
194
195 debug!(step = 6, "detect_slice");
197 let slice_edges = detect_slice(&records, allocations, &range_map);
198 graph.add_edges(slice_edges);
199 debug!(
200 step = 6,
201 edges = graph.edge_count(),
202 "detect_slice completed"
203 );
204
205 debug!(step = 7, "detect_clones");
207 let clone_edges = detect_clones(&records, &config.clone_config);
208 graph.add_edges(clone_edges);
209 debug!(
210 step = 7,
211 edges = graph.edge_count(),
212 "detect_clones completed"
213 );
214
215 debug!(step = 8, "detect_containers");
217 let container_edges = detect_containers(allocations, Some(config.container_config));
218 graph.add_edges(container_edges);
219 debug!(
220 step = 8,
221 edges = graph.edge_count(),
222 "detect_containers completed"
223 );
224
225 debug!(step = 9, "detect_variable_evolution");
227 let var_evolution_edges = detect_variable_evolution(allocations);
228 graph.add_edges(var_evolution_edges);
229 debug!(
230 step = 9,
231 edges = graph.edge_count(),
232 "detect_variable_evolution completed"
233 );
234
235 debug!(step = 10, "detect_shared");
237 let shared_edges = detect_shared(&records, &graph.edges);
238 graph.add_edges(shared_edges);
239 debug!(
240 step = 10,
241 edges = graph.edge_count(),
242 "detect_shared completed"
243 );
244
245 debug!("RelationGraphBuilder all steps completed");
246 graph
247 }
248}
249
250fn detect_variable_evolution(allocations: &[ActiveAllocation]) -> Vec<RelationEdge> {
256 use crate::analysis::relation_inference::Relation;
257 use std::collections::HashMap;
258
259 let mut var_groups: HashMap<String, Vec<usize>> = HashMap::new();
261 for (i, alloc) in allocations.iter().enumerate() {
262 if let Some(ref var_name) = alloc.var_name {
263 if !var_name.is_empty() && var_name != "unknown" {
264 var_groups.entry(var_name.clone()).or_default().push(i);
265 }
266 }
267 }
268
269 let mut edges = Vec::new();
270
271 for indices in var_groups.values() {
273 if indices.len() < 2 {
274 continue;
275 }
276
277 let mut sorted_indices: Vec<(usize, u64)> = indices
279 .iter()
280 .map(|&i| (i, allocations[i].allocated_at))
281 .collect();
282 sorted_indices.sort_by_key(|&(_, time)| time);
283
284 for window in sorted_indices.windows(2) {
286 let (from_idx, _) = window[0];
287 let (to_idx, _) = window[1];
288
289 edges.push(RelationEdge {
290 from: from_idx,
291 to: to_idx,
292 relation: Relation::Evolution,
293 });
294 }
295 }
296
297 edges
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::analysis::relation_inference::Relation;
304 use crate::core::types::TrackKind;
305
306 fn make_alloc(ptr: usize, size: usize) -> ActiveAllocation {
307 ActiveAllocation {
308 ptr: Some(ptr),
309 size,
310 kind: TrackKind::HeapOwner { ptr, size },
311 allocated_at: 1000,
312 var_name: None,
313 type_name: None,
314 thread_id: 0,
315 call_stack_hash: None,
316 module_path: None,
317 stack_ptr: None,
318 }
319 }
320
321 #[test]
322 fn test_build_empty() {
323 let graph = RelationGraphBuilder::build(&[], None);
324 assert_eq!(graph.edge_count(), 0);
325 }
326
327 #[test]
328 fn test_build_basic_owner_relationship() {
329 let buf1 = [0u8; 24];
331 let buf2 = vec![0u8; 1024];
332 let ptr1 = buf1.as_ptr() as usize;
333 let ptr2 = buf2.as_ptr() as usize;
334
335 let allocs = vec![make_alloc(ptr1, 24), make_alloc(ptr2, 1024)];
336 let graph = RelationGraphBuilder::build(&allocs, None);
337
338 assert_eq!(graph.edge_count(), 0);
341 }
342
343 #[test]
344 fn test_build_with_real_vec_metadata() {
345 let inner = vec![42u8; 256];
347 let ptr = inner.as_ptr() as usize;
348 let len = inner.len();
349 let cap = inner.capacity();
350
351 let mut metadata = [0u8; 24];
352 metadata[0..8].copy_from_slice(&ptr.to_le_bytes());
353 metadata[8..16].copy_from_slice(&len.to_le_bytes());
354 metadata[16..24].copy_from_slice(&cap.to_le_bytes());
355
356 let meta_ptr = metadata.as_ptr() as usize;
357 let inner_ptr = inner.as_ptr() as usize;
358
359 let allocs = vec![make_alloc(meta_ptr, 24), make_alloc(inner_ptr, 256)];
360 let graph = RelationGraphBuilder::build(&allocs, None);
361
362 assert!(graph.edge_count() <= 2);
365 }
366
367 #[test]
368 fn test_build_single_allocation() {
369 let allocs = vec![make_alloc(0x1000, 64)];
370 let graph = RelationGraphBuilder::build(&allocs, None);
371
372 assert_eq!(graph.edge_count(), 0);
374 }
375
376 #[test]
377 fn test_builder_with_default_config() {
378 let allocs = vec![make_alloc(0x1000, 64)];
379 let graph = RelationGraphBuilder::build(&allocs, Some(GraphBuilderConfig::default()));
380 assert_eq!(graph.edge_count(), 0);
381 }
382
383 #[test]
384 fn test_graph_node_count() {
385 let mut graph = RelationGraph::new();
386 graph.add_edge(0, 1, Relation::Owns);
387 graph.add_edge(1, 2, Relation::Slice);
388
389 let nodes = graph.all_nodes();
390 assert_eq!(nodes, vec![0, 1, 2]);
391 }
392}