Skip to main content

memscope_rs/analysis/relation_inference/
shared_detector.rs

1//! Shared relation detection — Arc/Rc shared ownership.
2//!
3//! Detects when multiple allocations share ownership of the same underlying data
4//! through Arc or Rc reference counting.
5//!
6//! # Detection Strategy
7//!
8//! Instead of hardcoding ArcInner offsets (which may change across Rust versions),
9//! we use a graph-based approach:
10//!
11//! 1. After Owner detection, find all nodes that have >= 2 inbound Owner edges.
12//! 2. If a node looks like an Arc/Rc control block (size ≈ 16 + inline data,
13//!    with small integer patterns in the first 16 bytes), its owners share it.
14//! 3. Add Shared edges between all pairs of owners.
15//!
16//! This avoids fragile offset assumptions and works with any Rust version.
17
18use crate::analysis::relation_inference::{InferenceRecord, Relation, RelationEdge};
19
20const MIN_SHARED_OWNERS: usize = 2;
21
22pub fn detect_shared(
23    records: &[InferenceRecord],
24    existing_edges: &[RelationEdge],
25) -> Vec<RelationEdge> {
26    let mut owners_of: Vec<Vec<usize>> = vec![Vec::new(); records.len()];
27    for edge in existing_edges {
28        if edge.relation == Relation::Owner {
29            owners_of[edge.to].push(edge.from);
30        }
31    }
32
33    let mut relations = Vec::new();
34
35    for (target_id, owners) in owners_of.iter().enumerate() {
36        if owners.len() < MIN_SHARED_OWNERS {
37            continue;
38        }
39
40        let target = &records[target_id];
41
42        if !looks_like_arc_rc(target) {
43            continue;
44        }
45
46        for i in 0..owners.len() {
47            for j in (i + 1)..owners.len() {
48                relations.push(RelationEdge {
49                    from: owners[i],
50                    to: owners[j],
51                    relation: Relation::Shared,
52                });
53            }
54        }
55    }
56
57    relations
58}
59
60fn looks_like_arc_rc(record: &InferenceRecord) -> bool {
61    if record.size < 16 || record.size > 1024 {
62        return false;
63    }
64
65    let memory = match &record.memory {
66        Some(m) => m,
67        None => return false,
68    };
69
70    if memory.len() < 16 {
71        return false;
72    }
73
74    let strong = memory.read_usize(0).unwrap_or(usize::MAX);
75    let weak = memory.read_usize(8).unwrap_or(usize::MAX);
76
77    let strong_valid = (1..=1000).contains(&strong);
78    let weak_valid = weak <= 100;
79
80    strong_valid && weak_valid
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::analysis::unsafe_inference::{OwnedMemoryView, TypeKind};
87
88    fn make_record(
89        id: usize,
90        ptr: usize,
91        size: usize,
92        memory: Option<Vec<u8>>,
93        type_kind: TypeKind,
94    ) -> InferenceRecord {
95        InferenceRecord {
96            id,
97            ptr,
98            size,
99            memory: memory.map(OwnedMemoryView::new),
100            type_kind,
101            confidence: 80,
102            call_stack_hash: None,
103            alloc_time: 0,
104        }
105    }
106
107    #[test]
108    fn test_shared_detection_basic() {
109        let mut arc_inner_data = vec![0u8; 48];
110        arc_inner_data[0..8].copy_from_slice(&2usize.to_le_bytes());
111        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
112
113        let records = vec![
114            make_record(0, 0x1000, 24, None, TypeKind::Vec),
115            make_record(1, 0x2000, 24, None, TypeKind::Vec),
116            make_record(2, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
117        ];
118
119        let existing_edges = vec![
120            RelationEdge {
121                from: 0,
122                to: 2,
123                relation: Relation::Owner,
124            },
125            RelationEdge {
126                from: 1,
127                to: 2,
128                relation: Relation::Owner,
129            },
130        ];
131
132        let shared = detect_shared(&records, &existing_edges);
133        assert_eq!(shared.len(), 1);
134        assert_eq!(shared[0].from, 0);
135        assert_eq!(shared[0].to, 1);
136        assert_eq!(shared[0].relation, Relation::Shared);
137    }
138
139    #[test]
140    fn test_no_shared_with_single_owner() {
141        let mut arc_inner_data = vec![0u8; 48];
142        arc_inner_data[0..8].copy_from_slice(&1usize.to_le_bytes());
143        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
144
145        let records = vec![
146            make_record(0, 0x1000, 24, None, TypeKind::Vec),
147            make_record(1, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
148        ];
149
150        let existing_edges = vec![RelationEdge {
151            from: 0,
152            to: 1,
153            relation: Relation::Owner,
154        }];
155
156        let shared = detect_shared(&records, &existing_edges);
157        assert!(shared.is_empty());
158    }
159
160    #[test]
161    fn test_no_shared_when_not_arc_like() {
162        let records = vec![
163            make_record(0, 0x1000, 24, None, TypeKind::Vec),
164            make_record(1, 0x2000, 24, None, TypeKind::Vec),
165            make_record(2, 0x3000, 4096, None, TypeKind::Buffer),
166        ];
167
168        let existing_edges = vec![
169            RelationEdge {
170                from: 0,
171                to: 2,
172                relation: Relation::Owner,
173            },
174            RelationEdge {
175                from: 1,
176                to: 2,
177                relation: Relation::Owner,
178            },
179        ];
180
181        let shared = detect_shared(&records, &existing_edges);
182        assert!(shared.is_empty());
183    }
184
185    #[test]
186    fn test_shared_three_owners() {
187        let mut arc_inner_data = vec![0u8; 48];
188        arc_inner_data[0..8].copy_from_slice(&3usize.to_le_bytes());
189        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
190
191        let records = vec![
192            make_record(0, 0x1000, 24, None, TypeKind::Vec),
193            make_record(1, 0x2000, 24, None, TypeKind::Vec),
194            make_record(2, 0x2500, 24, None, TypeKind::Vec),
195            make_record(3, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
196        ];
197
198        let existing_edges = vec![
199            RelationEdge {
200                from: 0,
201                to: 3,
202                relation: Relation::Owner,
203            },
204            RelationEdge {
205                from: 1,
206                to: 3,
207                relation: Relation::Owner,
208            },
209            RelationEdge {
210                from: 2,
211                to: 3,
212                relation: Relation::Owner,
213            },
214        ];
215
216        let shared = detect_shared(&records, &existing_edges);
217        assert_eq!(shared.len(), 3);
218    }
219
220    #[test]
221    fn test_looks_like_arc_rc_valid() {
222        let mut data = vec![0u8; 48];
223        data[0..8].copy_from_slice(&2usize.to_le_bytes());
224        data[8..16].copy_from_slice(&0usize.to_le_bytes());
225
226        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
227        assert!(looks_like_arc_rc(&record));
228    }
229
230    #[test]
231    fn test_looks_like_arc_rc_too_small() {
232        let record = make_record(0, 0x1000, 8, None, TypeKind::Buffer);
233        assert!(!looks_like_arc_rc(&record));
234    }
235
236    #[test]
237    fn test_looks_like_arc_rc_too_large() {
238        let record = make_record(0, 0x1000, 2048, None, TypeKind::Buffer);
239        assert!(!looks_like_arc_rc(&record));
240    }
241
242    #[test]
243    fn test_looks_like_arc_rc_no_memory() {
244        let record = make_record(0, 0x1000, 48, None, TypeKind::Buffer);
245        assert!(!looks_like_arc_rc(&record));
246    }
247
248    #[test]
249    fn test_looks_like_arc_rc_invalid_strong() {
250        let mut data = vec![0u8; 48];
251        data[0..8].copy_from_slice(&0usize.to_le_bytes());
252        data[8..16].copy_from_slice(&0usize.to_le_bytes());
253
254        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
255        assert!(!looks_like_arc_rc(&record));
256    }
257
258    #[test]
259    fn test_looks_like_arc_rc_invalid_weak() {
260        let mut data = vec![0u8; 48];
261        data[0..8].copy_from_slice(&1usize.to_le_bytes());
262        data[8..16].copy_from_slice(&9999usize.to_le_bytes());
263
264        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
265        assert!(!looks_like_arc_rc(&record));
266    }
267
268    #[test]
269    fn test_looks_like_arc_rc_strong_count_zero() {
270        // strong_count = 0 is invalid (no live references).
271        let mut data = vec![0u8; 48];
272        data[0..8].copy_from_slice(&0usize.to_le_bytes());
273        data[8..16].copy_from_slice(&0usize.to_le_bytes());
274
275        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
276        assert!(!looks_like_arc_rc(&record));
277    }
278
279    #[test]
280    fn test_looks_like_arc_rc_strong_count_boundary_1000() {
281        // strong_count = 1000 should be valid (upper boundary).
282        let mut data = vec![0u8; 48];
283        data[0..8].copy_from_slice(&1000usize.to_le_bytes());
284        data[8..16].copy_from_slice(&0usize.to_le_bytes());
285
286        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
287        assert!(looks_like_arc_rc(&record));
288    }
289
290    #[test]
291    fn test_looks_like_arc_rc_strong_count_exceeds_1000() {
292        // strong_count = 1001 should be invalid.
293        let mut data = vec![0u8; 48];
294        data[0..8].copy_from_slice(&1001usize.to_le_bytes());
295        data[8..16].copy_from_slice(&0usize.to_le_bytes());
296
297        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
298        assert!(!looks_like_arc_rc(&record));
299    }
300
301    #[test]
302    fn test_looks_like_arc_rc_weak_count_boundary_100() {
303        // weak_count = 100 should be valid.
304        let mut data = vec![0u8; 48];
305        data[0..8].copy_from_slice(&1usize.to_le_bytes());
306        data[8..16].copy_from_slice(&100usize.to_le_bytes());
307
308        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
309        assert!(looks_like_arc_rc(&record));
310    }
311
312    #[test]
313    fn test_looks_like_arc_rc_weak_count_exceeds_100() {
314        // weak_count = 101 should be invalid.
315        let mut data = vec![0u8; 48];
316        data[0..8].copy_from_slice(&1usize.to_le_bytes());
317        data[8..16].copy_from_slice(&101usize.to_le_bytes());
318
319        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
320        assert!(!looks_like_arc_rc(&record));
321    }
322
323    #[test]
324    fn test_no_shared_for_regular_vec_type() {
325        // Regular Vec types should NOT produce Shared edges even with multiple owners.
326        let records = vec![
327            make_record(0, 0x1000, 24, None, TypeKind::Vec),
328            make_record(1, 0x2000, 24, None, TypeKind::Vec),
329            // A buffer that doesn't look like Arc/Rc (random data, not strong/weak pattern).
330            make_record(2, 0x3000, 64, Some(vec![0xAAu8; 64]), TypeKind::Buffer),
331        ];
332
333        let existing_edges = vec![
334            RelationEdge {
335                from: 0,
336                to: 2,
337                relation: Relation::Owner,
338            },
339            RelationEdge {
340                from: 1,
341                to: 2,
342                relation: Relation::Owner,
343            },
344        ];
345
346        let shared = detect_shared(&records, &existing_edges);
347        assert!(
348            shared.is_empty(),
349            "Regular Vec buffer should not produce Shared edges"
350        );
351    }
352
353    #[test]
354    fn test_shared_detection_with_rc_like_data() {
355        // Rc has the same memory layout as Arc (strong, weak, data).
356        let mut rc_inner_data = vec![0u8; 32];
357        rc_inner_data[0..8].copy_from_slice(&2usize.to_le_bytes()); // strong = 2
358        rc_inner_data[8..16].copy_from_slice(&1usize.to_le_bytes()); // weak = 1
359
360        let records = vec![
361            make_record(0, 0x1000, 24, None, TypeKind::Vec),
362            make_record(1, 0x2000, 24, None, TypeKind::Vec),
363            make_record(2, 0x3000, 32, Some(rc_inner_data), TypeKind::Buffer),
364        ];
365
366        let existing_edges = vec![
367            RelationEdge {
368                from: 0,
369                to: 2,
370                relation: Relation::Owner,
371            },
372            RelationEdge {
373                from: 1,
374                to: 2,
375                relation: Relation::Owner,
376            },
377        ];
378
379        let shared = detect_shared(&records, &existing_edges);
380        assert_eq!(shared.len(), 1, "Rc-like data should produce Shared edge");
381        assert_eq!(shared[0].from, 0);
382        assert_eq!(shared[0].to, 1);
383    }
384
385    #[test]
386    fn test_no_shared_with_only_one_owner() {
387        // Even with Arc-like data, a single owner should not produce Shared.
388        let mut arc_inner_data = vec![0u8; 48];
389        arc_inner_data[0..8].copy_from_slice(&1usize.to_le_bytes());
390        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
391
392        let records = vec![
393            make_record(0, 0x1000, 24, None, TypeKind::Vec),
394            make_record(1, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
395        ];
396
397        let existing_edges = vec![RelationEdge {
398            from: 0,
399            to: 1,
400            relation: Relation::Owner,
401        }];
402
403        let shared = detect_shared(&records, &existing_edges);
404        assert!(
405            shared.is_empty(),
406            "Single owner should not produce Shared edges"
407        );
408    }
409}