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//! 4. Arc-specific detection: Find all allocations that look like Arc pointers
17//!    (size = 8, pointing to a valid address with ArcInner pattern).
18//!    Group them by the target address and add Shared edges.
19//!
20//! This avoids fragile offset assumptions and works with any Rust version.
21
22use crate::analysis::relation_inference::{InferenceRecord, Relation, RelationEdge};
23
24const MIN_SHARED_OWNERS: usize = 2;
25
26pub fn detect_shared(
27    records: &[InferenceRecord],
28    existing_edges: &[RelationEdge],
29) -> Vec<RelationEdge> {
30    let mut relations = Vec::new();
31
32    // Strategy 1: Owner-based detection (for Rc)
33    let mut owners_of: Vec<Vec<usize>> = vec![Vec::new(); records.len()];
34    for edge in existing_edges {
35        if edge.relation == Relation::Owns {
36            owners_of[edge.to].push(edge.from);
37        }
38    }
39
40    for (target_id, owners) in owners_of.iter().enumerate() {
41        if owners.len() < MIN_SHARED_OWNERS {
42            continue;
43        }
44
45        let target = &records[target_id];
46
47        if !looks_like_arc_rc(target) {
48            continue;
49        }
50
51        for i in 0..owners.len() {
52            for j in (i + 1)..owners.len() {
53                relations.push(RelationEdge {
54                    from: owners[i],
55                    to: owners[j],
56                    relation: Relation::Shares,
57                });
58            }
59        }
60    }
61
62    // Strategy 2: StackOwner-based detection (for Arc/Rc)
63    // Find all StackOwner allocations (Arc/Rc) and group them by their heap_ptr
64    // Since Arc/Rc objects are on stack (8 bytes) but point to heap, we can detect
65    // clones by finding multiple StackOwner objects pointing to the same heap allocation
66    // Note: We don't rely on type_kind here because UTI Engine may misclassify Arc as Vec
67    let mut stack_owners: Vec<(usize, usize)> = Vec::new(); // (record_id, heap_ptr)
68    for (i, record) in records.iter().enumerate() {
69        // Check if this allocation has stack_ptr metadata
70        // This indicates it's a StackOwner (Arc/Rc) tracked via the new StackOwner track_kind
71        if let Some(stack_ptr) = record.stack_ptr {
72            if stack_ptr > 0x1000 {
73                // Group by heap_ptr (record.ptr) which is the heap allocation
74                stack_owners.push((i, record.ptr));
75            }
76        }
77    }
78
79    // Group StackOwner allocations by heap pointer
80    let mut heap_to_records: std::collections::HashMap<usize, Vec<usize>> =
81        std::collections::HashMap::new();
82    for (record_id, heap_ptr) in stack_owners {
83        heap_to_records.entry(heap_ptr).or_default().push(record_id);
84    }
85
86    // Add ArcClone edges for StackOwner objects pointing to the same heap allocation
87    for (_heap_ptr, record_ids) in heap_to_records {
88        if record_ids.len() >= 2 {
89            for i in 0..record_ids.len() {
90                for j in (i + 1)..record_ids.len() {
91                    // Use ArcClone for StackOwner-based clone detection
92                    relations.push(RelationEdge {
93                        from: record_ids[i],
94                        to: record_ids[j],
95                        relation: Relation::ArcClone,
96                    });
97                }
98            }
99        }
100    }
101
102    relations
103}
104
105fn looks_like_arc_rc(record: &InferenceRecord) -> bool {
106    if record.size < 16 || record.size > 1024 {
107        return false;
108    }
109
110    let memory = match &record.memory {
111        Some(m) => m,
112        None => return false,
113    };
114
115    if memory.len() < 16 {
116        return false;
117    }
118
119    let strong = memory.read_usize(0).unwrap_or(usize::MAX);
120    let weak = memory.read_usize(8).unwrap_or(usize::MAX);
121
122    // 放宽阈值,增加检测范围
123    let strong_valid = (1..=10000).contains(&strong); // 从 1000 提升到 10000
124    let weak_valid = weak <= 1000; // 从 100 提升到 1000
125
126    strong_valid && weak_valid
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::analysis::unsafe_inference::{OwnedMemoryView, TypeKind};
133
134    fn make_record(
135        id: usize,
136        ptr: usize,
137        size: usize,
138        memory: Option<Vec<u8>>,
139        type_kind: TypeKind,
140    ) -> InferenceRecord {
141        InferenceRecord {
142            id,
143            ptr,
144            size,
145            memory: memory.map(OwnedMemoryView::new),
146            type_kind,
147            confidence: 100,
148            call_stack_hash: None,
149            alloc_time: 0,
150            stack_ptr: None,
151        }
152    }
153
154    fn make_record_with_stack_ptr(
155        id: usize,
156        ptr: usize,
157        stack_ptr: usize,
158        size: usize,
159        memory: Option<Vec<u8>>,
160        type_kind: TypeKind,
161    ) -> InferenceRecord {
162        InferenceRecord {
163            id,
164            ptr,
165            size,
166            memory: memory.map(OwnedMemoryView::new),
167            type_kind,
168            confidence: 100,
169            call_stack_hash: None,
170            alloc_time: 0,
171            stack_ptr: Some(stack_ptr),
172        }
173    }
174
175    #[test]
176    fn test_shared_detection_basic() {
177        let mut arc_inner_data = vec![0u8; 48];
178        arc_inner_data[0..8].copy_from_slice(&2usize.to_le_bytes());
179        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
180
181        let records = vec![
182            make_record(0, 0x1000, 24, None, TypeKind::Vec),
183            make_record(1, 0x2000, 24, None, TypeKind::Vec),
184            make_record(2, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
185        ];
186
187        let existing_edges = vec![
188            RelationEdge {
189                from: 0,
190                to: 2,
191                relation: Relation::Owns,
192            },
193            RelationEdge {
194                from: 1,
195                to: 2,
196                relation: Relation::Owns,
197            },
198        ];
199
200        let shared = detect_shared(&records, &existing_edges);
201        assert_eq!(shared.len(), 1);
202        assert_eq!(shared[0].from, 0);
203        assert_eq!(shared[0].to, 1);
204        assert_eq!(shared[0].relation, Relation::Shares);
205    }
206
207    #[test]
208    fn test_no_shared_with_single_owner() {
209        let mut arc_inner_data = vec![0u8; 48];
210        arc_inner_data[0..8].copy_from_slice(&1usize.to_le_bytes());
211        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
212
213        let records = vec![
214            make_record(0, 0x1000, 24, None, TypeKind::Vec),
215            make_record(1, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
216        ];
217
218        let existing_edges = vec![RelationEdge {
219            from: 0,
220            to: 1,
221            relation: Relation::Owns,
222        }];
223
224        let shared = detect_shared(&records, &existing_edges);
225        assert!(shared.is_empty());
226    }
227
228    #[test]
229    fn test_no_shared_when_not_arc_like() {
230        let records = vec![
231            make_record(0, 0x1000, 24, None, TypeKind::Vec),
232            make_record(1, 0x2000, 24, None, TypeKind::Vec),
233            make_record(2, 0x3000, 4096, None, TypeKind::Buffer),
234        ];
235
236        let existing_edges = vec![
237            RelationEdge {
238                from: 0,
239                to: 2,
240                relation: Relation::Owns,
241            },
242            RelationEdge {
243                from: 1,
244                to: 2,
245                relation: Relation::Owns,
246            },
247        ];
248
249        let shared = detect_shared(&records, &existing_edges);
250        assert!(shared.is_empty());
251    }
252
253    #[test]
254    fn test_shared_three_owners() {
255        let mut arc_inner_data = vec![0u8; 48];
256        arc_inner_data[0..8].copy_from_slice(&3usize.to_le_bytes());
257        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
258
259        let records = vec![
260            make_record(0, 0x1000, 24, None, TypeKind::Vec),
261            make_record(1, 0x2000, 24, None, TypeKind::Vec),
262            make_record(2, 0x2500, 24, None, TypeKind::Vec),
263            make_record(3, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
264        ];
265
266        let existing_edges = vec![
267            RelationEdge {
268                from: 0,
269                to: 3,
270                relation: Relation::Owns,
271            },
272            RelationEdge {
273                from: 1,
274                to: 3,
275                relation: Relation::Owns,
276            },
277            RelationEdge {
278                from: 2,
279                to: 3,
280                relation: Relation::Owns,
281            },
282        ];
283
284        let shared = detect_shared(&records, &existing_edges);
285        assert_eq!(shared.len(), 3);
286    }
287
288    #[test]
289    fn test_looks_like_arc_rc_valid() {
290        let mut data = vec![0u8; 48];
291        data[0..8].copy_from_slice(&2usize.to_le_bytes());
292        data[8..16].copy_from_slice(&0usize.to_le_bytes());
293
294        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
295        assert!(looks_like_arc_rc(&record));
296    }
297
298    #[test]
299    fn test_looks_like_arc_rc_too_small() {
300        let record = make_record(0, 0x1000, 8, None, TypeKind::Buffer);
301        assert!(!looks_like_arc_rc(&record));
302    }
303
304    #[test]
305    fn test_looks_like_arc_rc_too_large() {
306        let record = make_record(0, 0x1000, 2048, None, TypeKind::Buffer);
307        assert!(!looks_like_arc_rc(&record));
308    }
309
310    #[test]
311    fn test_looks_like_arc_rc_no_memory() {
312        let record = make_record(0, 0x1000, 48, None, TypeKind::Buffer);
313        assert!(!looks_like_arc_rc(&record));
314    }
315
316    #[test]
317    fn test_looks_like_arc_rc_invalid_strong() {
318        let mut data = vec![0u8; 48];
319        data[0..8].copy_from_slice(&0usize.to_le_bytes());
320        data[8..16].copy_from_slice(&0usize.to_le_bytes());
321
322        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
323        assert!(!looks_like_arc_rc(&record));
324    }
325
326    #[test]
327    fn test_looks_like_arc_rc_invalid_weak() {
328        let mut data = vec![0u8; 48];
329        data[0..8].copy_from_slice(&1usize.to_le_bytes());
330        data[8..16].copy_from_slice(&9999usize.to_le_bytes());
331
332        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
333        assert!(!looks_like_arc_rc(&record));
334    }
335
336    #[test]
337    fn test_looks_like_arc_rc_strong_count_zero() {
338        // strong_count = 0 is invalid (no live references).
339        let mut data = vec![0u8; 48];
340        data[0..8].copy_from_slice(&0usize.to_le_bytes());
341        data[8..16].copy_from_slice(&0usize.to_le_bytes());
342
343        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
344        assert!(!looks_like_arc_rc(&record));
345    }
346
347    #[test]
348    fn test_looks_like_arc_rc_strong_count_boundary_10000() {
349        // strong_count = 10000 should be valid (upper boundary).
350        let mut data = vec![0u8; 48];
351        data[0..8].copy_from_slice(&10000usize.to_le_bytes());
352        data[8..16].copy_from_slice(&0usize.to_le_bytes());
353
354        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
355        assert!(looks_like_arc_rc(&record));
356    }
357
358    #[test]
359    fn test_looks_like_arc_rc_strong_count_exceeds_10000() {
360        // strong_count = 10001 should be invalid.
361        let mut data = vec![0u8; 48];
362        data[0..8].copy_from_slice(&10001usize.to_le_bytes());
363        data[8..16].copy_from_slice(&0usize.to_le_bytes());
364
365        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
366        assert!(!looks_like_arc_rc(&record));
367    }
368
369    #[test]
370    fn test_looks_like_arc_rc_weak_count_boundary_1000() {
371        // weak_count = 1000 should be valid.
372        let mut data = vec![0u8; 48];
373        data[0..8].copy_from_slice(&1usize.to_le_bytes());
374        data[8..16].copy_from_slice(&1000usize.to_le_bytes());
375
376        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
377        assert!(looks_like_arc_rc(&record));
378    }
379
380    #[test]
381    fn test_looks_like_arc_rc_weak_count_exceeds_1000() {
382        // weak_count = 1001 should be invalid.
383        let mut data = vec![0u8; 48];
384        data[0..8].copy_from_slice(&1usize.to_le_bytes());
385        data[8..16].copy_from_slice(&1001usize.to_le_bytes());
386
387        let record = make_record(0, 0x1000, 48, Some(data), TypeKind::Buffer);
388        assert!(!looks_like_arc_rc(&record));
389    }
390
391    #[test]
392    fn test_no_shared_for_regular_vec_type() {
393        // Regular Vec types should NOT produce Shared edges even with multiple owners.
394        let records = vec![
395            make_record(0, 0x1000, 24, None, TypeKind::Vec),
396            make_record(1, 0x2000, 24, None, TypeKind::Vec),
397            // A buffer that doesn't look like Arc/Rc (random data, not strong/weak pattern).
398            make_record(2, 0x3000, 64, Some(vec![0xAAu8; 64]), TypeKind::Buffer),
399        ];
400
401        let existing_edges = vec![
402            RelationEdge {
403                from: 0,
404                to: 2,
405                relation: Relation::Owns,
406            },
407            RelationEdge {
408                from: 1,
409                to: 2,
410                relation: Relation::Owns,
411            },
412        ];
413
414        let shared = detect_shared(&records, &existing_edges);
415        assert!(
416            shared.is_empty(),
417            "Regular Vec buffer should not produce Shared edges"
418        );
419    }
420
421    #[test]
422    fn test_shared_detection_with_rc_like_data() {
423        // Rc has the same memory layout as Arc (strong, weak, data).
424        let mut rc_inner_data = vec![0u8; 32];
425        rc_inner_data[0..8].copy_from_slice(&2usize.to_le_bytes()); // strong = 2
426        rc_inner_data[8..16].copy_from_slice(&1usize.to_le_bytes()); // weak = 1
427
428        let records = vec![
429            make_record(0, 0x1000, 24, None, TypeKind::Vec),
430            make_record(1, 0x2000, 24, None, TypeKind::Vec),
431            make_record(2, 0x3000, 32, Some(rc_inner_data), TypeKind::Buffer),
432        ];
433
434        let existing_edges = vec![
435            RelationEdge {
436                from: 0,
437                to: 2,
438                relation: Relation::Owns,
439            },
440            RelationEdge {
441                from: 1,
442                to: 2,
443                relation: Relation::Owns,
444            },
445        ];
446
447        let shared = detect_shared(&records, &existing_edges);
448        assert_eq!(shared.len(), 1, "Rc-like data should produce Shared edge");
449        assert_eq!(shared[0].from, 0);
450        assert_eq!(shared[0].to, 1);
451    }
452
453    #[test]
454    fn test_stackowner_arc_clone_detection() {
455        // Test StackOwner-based Arc clone detection
456        // Multiple Arc objects on stack pointing to the same heap allocation
457        let heap_ptr = 0x3000; // ArcInner address
458        let stack_ptr1 = 0x7ff0000; // First Arc object on stack
459        let stack_ptr2 = 0x7ff0008; // Second Arc object on stack
460        let stack_ptr3 = 0x7ff0010; // Third Arc object on stack
461
462        let records = vec![
463            // Arc clone 1: stack_ptr1 -> heap_ptr
464            make_record_with_stack_ptr(0, heap_ptr, stack_ptr1, 48, None, TypeKind::Buffer),
465            // Arc clone 2: stack_ptr2 -> heap_ptr
466            make_record_with_stack_ptr(1, heap_ptr, stack_ptr2, 48, None, TypeKind::Buffer),
467            // Arc clone 3: stack_ptr3 -> heap_ptr
468            make_record_with_stack_ptr(2, heap_ptr, stack_ptr3, 48, None, TypeKind::Buffer),
469            // Unrelated allocation
470            make_record(3, 0x4000, 24, None, TypeKind::Vec),
471        ];
472
473        let existing_edges = vec![];
474
475        let shared = detect_shared(&records, &existing_edges);
476
477        // Should detect Arc clones and return ArcClone edges
478        // With 3 Arc clones pointing to the same heap, we should get edges between them
479        assert!(
480            !shared.is_empty(),
481            "StackOwner Arc clones should be detected"
482        );
483
484        // Verify that the edges are ArcClone type
485        let arc_clone_edges: Vec<_> = shared
486            .iter()
487            .filter(|e| matches!(e.relation, Relation::ArcClone))
488            .collect();
489        assert!(!arc_clone_edges.is_empty(), "Should have ArcClone edges");
490
491        println!(
492            "Detected {} ArcClone edges from 3 Arc clones",
493            arc_clone_edges.len()
494        );
495    }
496
497    #[test]
498    fn test_no_shared_with_only_one_owner() {
499        // Even with Arc-like data, a single owner should not produce Shared.
500        let mut arc_inner_data = vec![0u8; 48];
501        arc_inner_data[0..8].copy_from_slice(&1usize.to_le_bytes());
502        arc_inner_data[8..16].copy_from_slice(&0usize.to_le_bytes());
503
504        let records = vec![
505            make_record(0, 0x1000, 24, None, TypeKind::Vec),
506            make_record(1, 0x3000, 48, Some(arc_inner_data), TypeKind::Buffer),
507        ];
508
509        let existing_edges = vec![RelationEdge {
510            from: 0,
511            to: 1,
512            relation: Relation::Owns,
513        }];
514
515        let shared = detect_shared(&records, &existing_edges);
516        assert!(
517            shared.is_empty(),
518            "Single owner should not produce Shared edges"
519        );
520    }
521}