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