memscope_rs/analysis/relation_inference/
pointer_scan.rs1use crate::analysis::relation_inference::{RangeMap, Relation, RelationEdge};
16use crate::analysis::unsafe_inference::{is_valid_ptr, OwnedMemoryView};
17
18const MIN_VALID_POINTER: usize = 0x1000;
19
20const POINTER_ALIGNMENT: usize = 8;
23
24pub struct InferenceRecord {
26 pub id: usize,
28 pub ptr: usize,
30 pub size: usize,
32 pub memory: Option<OwnedMemoryView>,
34 pub type_kind: crate::analysis::unsafe_inference::TypeKind,
36 pub confidence: u8,
38 pub call_stack_hash: Option<u64>,
40 pub alloc_time: u64,
42}
43
44pub fn detect_owner(record: &InferenceRecord, range_map: &RangeMap) -> Vec<RelationEdge> {
59 detect_owner_impl(record, range_map, false)
60}
61
62fn detect_owner_impl(
63 record: &InferenceRecord,
64 range_map: &RangeMap,
65 skip_validation: bool,
66) -> Vec<RelationEdge> {
67 let mut relations = Vec::new();
68 let mut seen_targets = std::collections::HashSet::new();
69
70 let memory = match &record.memory {
71 Some(m) => m,
72 None => return relations,
73 };
74
75 let ptr_size = std::mem::size_of::<usize>();
76 if memory.len() < ptr_size {
77 return relations;
78 }
79
80 for offset in (0..memory.len()).step_by(ptr_size) {
81 if offset + ptr_size > memory.len() {
82 break;
83 }
84
85 let ptr_val = memory.read_usize(offset);
86 let Some(ptr_val) = ptr_val else {
87 continue;
88 };
89
90 if ptr_val == 0 || ptr_val < MIN_VALID_POINTER {
91 continue;
92 }
93
94 if ptr_val % POINTER_ALIGNMENT != 0 {
95 continue;
96 }
97
98 if !skip_validation && !is_valid_ptr(ptr_val) {
100 continue;
101 }
102
103 if let Some(target_id) = range_map.find_containing(ptr_val) {
104 if target_id == record.id {
105 continue;
106 }
107 if seen_targets.insert(target_id) {
108 relations.push(RelationEdge {
109 from: record.id,
110 to: target_id,
111 relation: Relation::Owner,
112 });
113 }
114 }
115 }
116
117 relations
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::analysis::unsafe_inference::TypeKind;
124 use crate::snapshot::types::ActiveAllocation;
125
126 fn make_record(id: usize, ptr: usize, size: usize, memory: Vec<u8>) -> InferenceRecord {
127 InferenceRecord {
128 id,
129 ptr,
130 size,
131 memory: Some(OwnedMemoryView::new(memory)),
132 type_kind: TypeKind::Unknown,
133 confidence: 0,
134 call_stack_hash: None,
135 alloc_time: 0,
136 }
137 }
138
139 fn make_alloc(ptr: usize, size: usize) -> ActiveAllocation {
140 ActiveAllocation {
141 ptr,
142 size,
143 allocated_at: 0,
144 var_name: None,
145 type_name: None,
146 thread_id: 0,
147 call_stack_hash: None,
148 }
149 }
150
151 #[test]
152 #[cfg(target_os = "macos")]
153 fn test_detect_owner_basic() {
154 let target_ptr: usize = 0x5000;
155 let mut mem = vec![0u8; 24];
156 mem[0..8].copy_from_slice(&target_ptr.to_le_bytes());
157
158 let record = make_record(0, 0x1000, 24, mem);
159 let allocs = vec![make_alloc(0x1000, 24), make_alloc(0x5000, 1024)];
160 let range_map = RangeMap::new(&allocs);
161
162 let edges = detect_owner_impl(&record, &range_map, true);
163 assert_eq!(edges.len(), 1);
164 assert_eq!(edges[0].from, 0);
165 assert_eq!(edges[0].to, 1);
166 assert_eq!(edges[0].relation, Relation::Owner);
167 }
168
169 #[test]
170 fn test_detect_owner_no_memory() {
171 let record = InferenceRecord {
172 id: 0,
173 ptr: 0x1000,
174 size: 24,
175 memory: None,
176 type_kind: TypeKind::Unknown,
177 confidence: 0,
178 call_stack_hash: None,
179 alloc_time: 0,
180 };
181 let range_map = RangeMap::new(&[]);
182 let edges = detect_owner_impl(&record, &range_map, true);
183 assert!(edges.is_empty());
184 }
185
186 #[test]
187 fn test_detect_owner_no_valid_pointers() {
188 let record = make_record(0, 0x1000, 24, vec![0u8; 24]);
189 let allocs = vec![make_alloc(0x5000, 100)];
190 let range_map = RangeMap::new(&allocs);
191
192 let edges = detect_owner_impl(&record, &range_map, true);
193 assert!(edges.is_empty());
194 }
195
196 #[test]
197 #[cfg(target_os = "macos")]
198 fn test_detect_owner_multiple_pointers() {
199 let ptr1: usize = 0x5000;
200 let ptr2: usize = 0x6000;
201 let mut mem = vec![0u8; 24];
202 mem[0..8].copy_from_slice(&ptr1.to_le_bytes());
203 mem[8..16].copy_from_slice(&ptr2.to_le_bytes());
204
205 let record = make_record(0, 0x1000, 24, mem);
206 let allocs = vec![
207 make_alloc(0x1000, 24),
208 make_alloc(0x5000, 100),
209 make_alloc(0x6000, 100),
210 ];
211 let range_map = RangeMap::new(&allocs);
212
213 let edges = detect_owner_impl(&record, &range_map, true);
214 assert_eq!(edges.len(), 2);
215 }
216
217 #[test]
218 fn test_detect_owner_no_self_reference() {
219 let self_ptr: usize = 0x1000;
220 let mut mem = vec![0u8; 24];
221 mem[0..8].copy_from_slice(&self_ptr.to_le_bytes());
222
223 let record = make_record(0, 0x1000, 24, mem);
224 let allocs = vec![make_alloc(0x1000, 24)];
225 let range_map = RangeMap::new(&allocs);
226
227 let edges = detect_owner_impl(&record, &range_map, true);
228 assert!(edges.is_empty());
229 }
230
231 #[test]
232 fn test_detect_owner_small_memory() {
233 let record = make_record(0, 0x1000, 4, vec![0u8; 4]);
234 let range_map = RangeMap::new(&[]);
235 let edges = detect_owner_impl(&record, &range_map, true);
236 assert!(edges.is_empty());
237 }
238
239 #[test]
240 #[cfg(target_os = "macos")]
241 fn test_detect_owner_duplicate_pointer_same_target() {
242 let target_ptr: usize = 0x5000;
243 let mut mem = vec![0u8; 24];
244 mem[0..8].copy_from_slice(&target_ptr.to_le_bytes());
245 mem[8..16].copy_from_slice(&target_ptr.to_le_bytes());
246
247 let record = make_record(0, 0x1000, 24, mem);
248 let allocs = vec![make_alloc(0x1000, 24), make_alloc(0x5000, 100)];
249 let range_map = RangeMap::new(&allocs);
250
251 let edges = detect_owner_impl(&record, &range_map, true);
252 assert_eq!(edges.len(), 1);
253 assert_eq!(edges[0].to, 1);
254 assert_eq!(edges[0].from, 0);
255 }
256
257 #[test]
258 fn test_detect_owner_unaligned_pointer_rejected() {
259 let mut mem = vec![0u8; 24];
261 let unaligned_ptr: usize = 0x5003; mem[0..8].copy_from_slice(&unaligned_ptr.to_le_bytes());
263
264 let record = make_record(0, 0x1000, 24, mem);
265 let allocs = vec![make_alloc(0x1000, 24), make_alloc(0x5000, 100)];
266 let range_map = RangeMap::new(&allocs);
267
268 let edges = detect_owner_impl(&record, &range_map, true);
269 assert!(edges.is_empty(), "Unaligned pointer should be rejected");
270 }
271
272 #[test]
273 fn test_detect_owner_pointer_to_gap_rejected() {
274 let gap_ptr: usize = 0x5500; let mut mem = vec![0u8; 24];
277 mem[0..8].copy_from_slice(&gap_ptr.to_le_bytes());
278
279 let record = make_record(0, 0x1000, 24, mem);
280 let allocs = vec![
281 make_alloc(0x1000, 24),
282 make_alloc(0x5000, 100),
283 make_alloc(0x6000, 100),
284 ];
285 let range_map = RangeMap::new(&allocs);
286
287 let edges = detect_owner_impl(&record, &range_map, true);
288 assert!(
289 edges.is_empty(),
290 "Pointer to gap should not match any allocation"
291 );
292 }
293
294 #[test]
295 #[cfg(target_os = "macos")]
296 fn test_detect_owner_multiple_different_targets() {
297 let ptr1: usize = 0x5000;
299 let ptr2: usize = 0x6000;
300 let ptr3: usize = 0x7000;
301 let mut mem = vec![0u8; 32];
302 mem[0..8].copy_from_slice(&ptr1.to_le_bytes());
303 mem[8..16].copy_from_slice(&ptr2.to_le_bytes());
304 mem[16..24].copy_from_slice(&ptr3.to_le_bytes());
305
306 let record = make_record(0, 0x1000, 32, mem);
307 let allocs = vec![
308 make_alloc(0x1000, 32),
309 make_alloc(0x5000, 100),
310 make_alloc(0x6000, 100),
311 make_alloc(0x7000, 100),
312 ];
313 let range_map = RangeMap::new(&allocs);
314
315 let edges = detect_owner_impl(&record, &range_map, true);
316 assert_eq!(edges.len(), 3);
317 let targets: Vec<_> = edges.iter().map(|e| e.to).collect();
318 assert!(targets.contains(&1));
319 assert!(targets.contains(&2));
320 assert!(targets.contains(&3));
321 }
322
323 #[test]
324 fn test_detect_owner_null_pointer_skipped() {
325 let mut mem = vec![0u8; 24];
326 let valid_ptr: usize = 0x5000;
328 mem[8..16].copy_from_slice(&valid_ptr.to_le_bytes());
329
330 let record = make_record(0, 0x1000, 24, mem);
331 let allocs = vec![make_alloc(0x1000, 24), make_alloc(0x5000, 100)];
332 let range_map = RangeMap::new(&allocs);
333
334 let edges = detect_owner_impl(&record, &range_map, true);
335 assert_eq!(edges.len(), 1);
336 assert_eq!(edges[0].to, 1);
337 }
338
339 #[test]
340 fn test_detect_owner_low_address_skipped() {
341 let mut mem = vec![0u8; 24];
342 let low_ptr: usize = 0x100;
344 mem[0..8].copy_from_slice(&low_ptr.to_le_bytes());
345
346 let record = make_record(0, 0x1000, 24, mem);
347 let allocs = vec![make_alloc(0x100, 100), make_alloc(0x1000, 24)];
348 let range_map = RangeMap::new(&allocs);
349
350 let edges = detect_owner_impl(&record, &range_map, true);
351 assert!(edges.is_empty(), "Low address pointer should be skipped");
352 }
353}