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