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 Self::build_with_scans(allocations, config, scan_results)
133 }
134
135 pub(crate) fn from_observations(allocations: &[ActiveAllocation]) -> RelationGraph {
139 Self::build_with_scans(allocations, GraphBuilderConfig::default(), Vec::new())
140 }
141
142 fn build_with_scans(
143 allocations: &[ActiveAllocation],
144 config: GraphBuilderConfig,
145 scan_results: Vec<ScanResult>,
146 ) -> RelationGraph {
147 debug!(step = 2, "Building scan_map");
149 let scan_map: std::collections::HashMap<(usize, usize), &ScanResult> = scan_results
150 .iter()
151 .map(|scan| ((scan.ptr, scan.size), scan))
152 .collect();
153 debug!(step = 2, "scan_map completed");
154
155 debug!(step = 3, "Running UTI Engine");
158 let records: Vec<InferenceRecord> = allocations
159 .iter()
160 .enumerate()
161 .map(|(id, alloc)| {
162 let scan = scan_map.get(&(alloc.ptr.unwrap_or(0), alloc.size));
163
164 let (type_kind, confidence) =
165 if let Some(memory) = scan.and_then(|s| s.memory.as_deref()) {
166 let view = MemoryView::new(memory);
167 let guess = UnsafeInferenceEngine::infer_single(&view, alloc.size);
168 (guess.kind, guess.confidence)
169 } else {
170 (TypeKind::Unknown, 0)
171 };
172
173 InferenceRecord {
174 id,
175 ptr: alloc.ptr.unwrap_or(0),
176 size: alloc.size,
177 memory: scan
178 .and_then(|s| s.memory.as_deref())
179 .map(OwnedMemoryView::copy_scan),
180 type_kind,
181 confidence,
182 call_stack_hash: alloc.call_stack_hash,
183 alloc_time: alloc.allocated_at,
184 stack_ptr: alloc.stack_ptr,
185 }
186 })
187 .collect();
188 debug!(step = 3, records = records.len(), "UTI Engine completed");
189
190 debug!(step = 4, "Building RangeMap");
192 let range_map = RangeMap::new(allocations);
193 debug!(step = 4, "RangeMap completed");
194
195 let mut graph = RelationGraph::new();
197
198 debug!(step = 5, "detect_owner");
200 for record in &records {
201 let edges = detect_owner(record, &range_map);
202 graph.add_edges(edges);
203 }
204 debug!(
205 step = 5,
206 edges = graph.edge_count(),
207 "detect_owner completed"
208 );
209
210 debug!(step = 6, "detect_slice");
212 let slice_edges = detect_slice(&records, allocations, &range_map);
213 graph.add_edges(slice_edges);
214 debug!(
215 step = 6,
216 edges = graph.edge_count(),
217 "detect_slice completed"
218 );
219
220 debug!(step = 7, "detect_clones");
222 let clone_edges = detect_clones(&records, &config.clone_config);
223 graph.add_edges(clone_edges);
224 debug!(
225 step = 7,
226 edges = graph.edge_count(),
227 "detect_clones completed"
228 );
229
230 debug!(step = 8, "detect_containers");
232 let container_edges = detect_containers(allocations, Some(config.container_config));
233 graph.add_edges(container_edges);
234 debug!(
235 step = 8,
236 edges = graph.edge_count(),
237 "detect_containers completed"
238 );
239
240 debug!(step = 9, "detect_variable_evolution");
242 let var_evolution_edges = detect_variable_evolution(allocations);
243 graph.add_edges(var_evolution_edges);
244 debug!(
245 step = 9,
246 edges = graph.edge_count(),
247 "detect_variable_evolution completed"
248 );
249
250 debug!(step = 10, "detect_shared");
252 let shared_edges = detect_shared(&records, &graph.edges);
253 graph.add_edges(shared_edges);
254 debug!(
255 step = 10,
256 edges = graph.edge_count(),
257 "detect_shared completed"
258 );
259
260 debug!("RelationGraphBuilder all steps completed");
261 graph
262 }
263}
264
265fn detect_variable_evolution(allocations: &[ActiveAllocation]) -> Vec<RelationEdge> {
271 use crate::analysis::relation_inference::Relation;
272 use std::collections::HashMap;
273
274 let mut var_groups: HashMap<String, Vec<usize>> = HashMap::new();
276 for (i, alloc) in allocations.iter().enumerate() {
277 if let Some(ref var_name) = alloc.var_name {
278 if !var_name.is_empty() && var_name != "unknown" {
279 var_groups.entry(var_name.clone()).or_default().push(i);
280 }
281 }
282 }
283
284 let mut edges = Vec::new();
285
286 for indices in var_groups.values() {
288 if indices.len() < 2 {
289 continue;
290 }
291
292 let mut sorted_indices: Vec<(usize, u64)> = indices
294 .iter()
295 .map(|&i| (i, allocations[i].allocated_at))
296 .collect();
297 sorted_indices.sort_by_key(|&(_, time)| time);
298
299 for window in sorted_indices.windows(2) {
301 let (from_idx, _) = window[0];
302 let (to_idx, _) = window[1];
303
304 edges.push(RelationEdge {
305 from: from_idx,
306 to: to_idx,
307 relation: Relation::Evolution,
308 });
309 }
310 }
311
312 edges
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::analysis::relation_inference::Relation;
319 use crate::core::types::TrackKind;
320
321 fn make_alloc(ptr: usize, size: usize) -> ActiveAllocation {
322 ActiveAllocation {
323 ptr: Some(ptr),
324 size,
325 kind: TrackKind::HeapOwner { ptr, size },
326 allocated_at: 1000,
327 var_name: None,
328 type_name: None,
329 thread_id: 0,
330 call_stack_hash: None,
331 module_path: None,
332 stack_ptr: None,
333 }
334 }
335
336 #[test]
337 fn test_build_empty() {
338 let graph = RelationGraphBuilder::build(&[], None);
339 assert_eq!(graph.edge_count(), 0);
340 }
341
342 #[test]
343 fn test_build_basic_owner_relationship() {
344 let buf1 = [0u8; 24];
346 let buf2 = vec![0u8; 1024];
347 let ptr1 = buf1.as_ptr() as usize;
348 let ptr2 = buf2.as_ptr() as usize;
349
350 let allocs = vec![make_alloc(ptr1, 24), make_alloc(ptr2, 1024)];
351 let graph = RelationGraphBuilder::build(&allocs, None);
352
353 assert_eq!(graph.edge_count(), 0);
356 }
357
358 #[test]
359 fn test_build_with_real_vec_metadata() {
360 let inner = vec![42u8; 256];
362 let ptr = inner.as_ptr() as usize;
363 let len = inner.len();
364 let cap = inner.capacity();
365
366 let mut metadata = [0u8; 24];
367 metadata[0..8].copy_from_slice(&ptr.to_le_bytes());
368 metadata[8..16].copy_from_slice(&len.to_le_bytes());
369 metadata[16..24].copy_from_slice(&cap.to_le_bytes());
370
371 let meta_ptr = metadata.as_ptr() as usize;
372 let inner_ptr = inner.as_ptr() as usize;
373
374 let allocs = vec![make_alloc(meta_ptr, 24), make_alloc(inner_ptr, 256)];
375 let graph = RelationGraphBuilder::build(&allocs, None);
376
377 assert!(graph.edge_count() <= 2);
380 }
381
382 #[test]
383 fn test_build_single_allocation() {
384 let allocs = vec![make_alloc(0x1000, 64)];
385 let graph = RelationGraphBuilder::build(&allocs, None);
386
387 assert_eq!(graph.edge_count(), 0);
389 }
390
391 #[test]
392 fn test_builder_with_default_config() {
393 let allocs = vec![make_alloc(0x1000, 64)];
394 let graph = RelationGraphBuilder::build(&allocs, Some(GraphBuilderConfig::default()));
395 assert_eq!(graph.edge_count(), 0);
396 }
397
398 #[test]
399 fn test_graph_node_count() {
400 let mut graph = RelationGraph::new();
401 graph.add_edge(0, 1, Relation::Owns);
402 graph.add_edge(1, 2, Relation::Slice);
403
404 let nodes = graph.all_nodes();
405 assert_eq!(nodes, vec![0, 1, 2]);
406 }
407}