1use serde::{Deserialize, Serialize};
36
37use super::node_id::NodeId;
38
39pub type ObjectId = NodeId;
46
47#[repr(u8)]
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum OwnershipOp {
51 Create,
53 Drop,
55 RcClone,
57 ArcClone,
59 Move,
61 SharedBorrow,
63 MutBorrow,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69pub struct OwnershipEvent {
70 pub ts: u64,
72 pub op: OwnershipOp,
74 pub src: ObjectId,
76 pub dst: Option<ObjectId>,
78}
79
80impl OwnershipEvent {
81 pub fn new(ts: u64, op: OwnershipOp, src: ObjectId, dst: Option<ObjectId>) -> Self {
83 Self { ts, op, src, dst }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct Node {
90 pub id: ObjectId,
92 pub type_name: String,
94 pub size: usize,
96 pub stack_ptr: Option<usize>,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
102pub enum EdgeKind {
103 Owns,
105 Contains,
107 Borrows,
109 RcClone,
111 ArcClone,
113 Move,
115 SharedBorrow,
117 MutBorrow,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct Edge {
124 pub from: ObjectId,
126 pub to: ObjectId,
128 pub op: EdgeKind,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct OwnershipGraph {
135 pub nodes: Vec<Node>,
137 pub edges: Vec<Edge>,
139 pub cycles: Vec<Vec<ObjectId>>,
141 pub arc_clone_count: usize,
143}
144
145impl OwnershipGraph {
146 pub fn from_view(view: &crate::view::MemoryView) -> Self {
151 let allocations = view.allocations();
152 let passports: Vec<(ObjectId, String, usize, Vec<OwnershipEvent>)> = allocations
153 .iter()
154 .filter_map(|a| {
155 a.ptr.map(|ptr| {
156 let id = ObjectId::from_ptr(ptr);
157 let type_name = a.type_name.clone().unwrap_or_else(|| "unknown".to_string());
158 let event = OwnershipEvent::new(a.allocated_at, OwnershipOp::Create, id, None);
160 (id, type_name, a.size, vec![event])
161 })
162 })
163 .collect();
164
165 Self::build(&passports)
166 }
167
168 pub fn build<T: AsRef<[OwnershipEvent]>>(passports: &[(ObjectId, String, usize, T)]) -> Self {
170 Self::build_with_analysis(passports, None, None)
171 }
172
173 pub fn build_with_analysis<T: AsRef<[OwnershipEvent]>>(
187 passports: &[(ObjectId, String, usize, T)],
188 rustdoc_json_path: Option<&str>,
189 source_code: Option<&str>,
190 ) -> Self {
191 let mut nodes = Vec::new();
192 let mut edges = Vec::new();
193 let mut arc_clone_count = 0;
194
195 let type_db = if let Some(path) = rustdoc_json_path {
197 use crate::analysis::ownership_analyzer::RustdocExtractor;
198 use std::path::PathBuf;
199 let extractor = RustdocExtractor::new(PathBuf::from(path));
200 extractor.extract().ok()
201 } else {
202 None
203 };
204
205 let ast_ops = if let Some(code) = source_code {
207 use crate::analysis::ownership_analyzer::AstAnalyzer;
208 let analyzer = AstAnalyzer::new(code.to_string());
209 Some(analyzer.analyze())
210 } else {
211 None
212 };
213
214 let mut ownership_state: std::collections::HashMap<NodeId, bool> =
216 std::collections::HashMap::new();
217
218 for (id, type_name, size, events) in passports {
219 nodes.push(Node {
221 id: *id,
222 type_name: type_name.clone(),
223 size: *size,
224 stack_ptr: None,
225 });
226
227 let is_copy_type = if let Some(ref db) = type_db {
229 db.types.get(type_name).map(|t| t.is_copy).unwrap_or(false)
230 } else {
231 type_name.contains("i32")
233 || type_name.contains("i64")
234 || type_name.contains("f32")
235 || type_name.contains("f64")
236 || type_name.contains("bool")
237 || type_name.contains("usize")
238 };
239
240 for event in events.as_ref() {
242 match event.op {
243 OwnershipOp::RcClone => {
244 if let Some(dst) = event.dst {
245 edges.push(Edge {
246 from: event.src,
247 to: dst,
248 op: EdgeKind::RcClone,
249 });
250 }
251 }
252 OwnershipOp::ArcClone => {
253 arc_clone_count += 1;
254 if let Some(dst) = event.dst {
255 edges.push(Edge {
256 from: event.src,
257 to: dst,
258 op: EdgeKind::ArcClone,
259 });
260 }
261 }
262 OwnershipOp::Move => {
263 if let Some(dst) = event.dst {
264 if !is_copy_type {
266 edges.push(Edge {
267 from: event.src,
268 to: dst,
269 op: EdgeKind::Move,
270 });
271 ownership_state.insert(event.src, false); ownership_state.insert(dst, true); }
275 }
276 }
277 OwnershipOp::SharedBorrow => {
278 if let Some(dst) = event.dst {
279 edges.push(Edge {
280 from: event.src,
281 to: dst,
282 op: EdgeKind::SharedBorrow,
283 });
284 }
285 }
286 OwnershipOp::MutBorrow => {
287 if let Some(dst) = event.dst {
288 edges.push(Edge {
289 from: event.src,
290 to: dst,
291 op: EdgeKind::MutBorrow,
292 });
293 }
294 }
295 OwnershipOp::Create | OwnershipOp::Drop => {
296 }
298 }
299 }
300 }
301
302 if let Some(ref ops) = ast_ops {
304 use crate::analysis::ownership_analyzer::OwnershipOp as AstOwnershipOp;
305 for op in ops {
306 match op {
307 AstOwnershipOp::Move {
308 target: _,
309 source: _,
310 line: _,
311 } => {
312 }
316 AstOwnershipOp::CallMove { .. } => {
317 }
319 AstOwnershipOp::Borrow { .. } => {
320 }
322 }
323 }
324 }
325
326 Self::compress_clone_chains(&mut edges);
328
329 let cycles = Self::detect_cycles(&edges);
331
332 OwnershipGraph {
333 nodes,
334 edges,
335 cycles,
336 arc_clone_count,
337 }
338 }
339
340 fn compress_clone_chains(edges: &mut Vec<Edge>) {
344 if edges.len() < 2 {
345 return;
346 }
347
348 let mut result: Vec<Edge> = Vec::with_capacity(edges.len());
349 let mut i = 0;
350
351 while i < edges.len() {
352 let mut current = edges[i].clone();
353
354 while i + 1 < edges.len()
356 && current.op == edges[i + 1].op
357 && current.to == edges[i + 1].from
358 {
359 current.to = edges[i + 1].to;
361 i += 1;
362 }
363
364 result.push(current);
365 i += 1;
366 }
367
368 *edges = result;
369 }
370
371 fn detect_cycles(edges: &[Edge]) -> Vec<Vec<ObjectId>> {
373 use crate::analysis::relationship_cycle_detector;
374
375 if edges.is_empty() {
376 return Vec::new();
377 }
378
379 let relationships: Vec<(String, String, String)> = edges
381 .iter()
382 .map(|e| {
383 (
384 format!("0x{:x}", e.from.0),
385 format!("0x{:x}", e.to.0),
386 format!("{:?}", e.op).to_lowercase(),
387 )
388 })
389 .collect();
390
391 let result = relationship_cycle_detector::detect_cycles_with_indices(&relationships);
392
393 let mut cycles = Vec::new();
395 let mut obj_id_map: std::collections::HashMap<String, ObjectId> =
396 std::collections::HashMap::new();
397
398 for edge in edges {
399 obj_id_map.insert(format!("0x{:x}", edge.from.0), edge.from);
400 obj_id_map.insert(format!("0x{:x}", edge.to.0), edge.to);
401 }
402
403 for (from_idx, to_idx) in result.cycle_edges {
404 if let (Some(from_label), Some(to_label)) = (
405 result.node_labels.get(from_idx),
406 result.node_labels.get(to_idx),
407 ) {
408 if let (Some(from_id), Some(to_id)) =
409 (obj_id_map.get(from_label), obj_id_map.get(to_label))
410 {
411 cycles.push(vec![*from_id, *to_id]);
413 }
414 }
415 }
416
417 cycles
418 }
419
420 pub fn has_arc_clone_storm(&self, threshold: usize) -> bool {
422 self.arc_clone_count > threshold
423 }
424
425 pub fn rc_clones(&self) -> Vec<&Edge> {
427 self.edges
428 .iter()
429 .filter(|e| e.op == EdgeKind::RcClone)
430 .collect()
431 }
432
433 pub fn arc_clones(&self) -> Vec<&Edge> {
435 self.edges
436 .iter()
437 .filter(|e| e.op == EdgeKind::ArcClone)
438 .collect()
439 }
440
441 pub fn diagnostics(&self, arc_storm_threshold: usize) -> OwnershipDiagnostics {
443 let mut issues = Vec::new();
444
445 for cycle in &self.cycles {
447 let cycle_type = self.detect_cycle_type(cycle);
448 issues.push(DiagnosticIssue::RcCycle {
449 nodes: cycle.clone(),
450 cycle_type,
451 });
452 }
453
454 let arc_clone_count = self.arc_clones().len();
456 if arc_clone_count > arc_storm_threshold {
457 issues.push(DiagnosticIssue::ArcCloneStorm {
458 clone_count: arc_clone_count,
459 threshold: arc_storm_threshold,
460 });
461 }
462
463 OwnershipDiagnostics {
464 issues,
465 total_nodes: self.nodes.len(),
466 total_edges: self.edges.len(),
467 rc_clone_count: self.rc_clones().len(),
468 arc_clone_count,
469 }
470 }
471
472 fn detect_cycle_type(&self, cycle: &[ObjectId]) -> CycleType {
474 for edge in &self.edges {
476 if cycle.contains(&edge.from)
477 && cycle.contains(&edge.to)
478 && edge.op == EdgeKind::RcClone
479 {
480 return CycleType::Rc;
481 }
482 }
483 CycleType::Arc
484 }
485
486 pub fn find_root_cause(&self) -> Option<RootCauseChain> {
488 if self.arc_clone_count > 50 {
490 return Some(RootCauseChain {
491 root_cause: RootCause::ArcCloneStorm,
492 description: format!(
493 "Arc clone storm detected: {} clones causing memory proliferation",
494 self.arc_clone_count
495 ),
496 impact: format!(
497 "Potential memory spike from {} Arc clone operations",
498 self.arc_clone_count
499 ),
500 });
501 }
502
503 if !self.cycles.is_empty() {
505 return Some(RootCauseChain {
506 root_cause: RootCause::RcCycle,
507 description: format!(
508 "Rc retain cycle detected: {} cycles found",
509 self.cycles.len()
510 ),
511 impact: "Memory leak due to reference count cycles".to_string(),
512 });
513 }
514
515 None
516 }
517}
518
519#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
521pub enum CycleType {
522 Rc,
524 Arc,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize)]
530pub enum DiagnosticIssue {
531 RcCycle {
533 nodes: Vec<ObjectId>,
534 cycle_type: CycleType,
535 },
536 ArcCloneStorm {
538 clone_count: usize,
539 threshold: usize,
540 },
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct OwnershipDiagnostics {
546 pub issues: Vec<DiagnosticIssue>,
548 pub total_nodes: usize,
550 pub total_edges: usize,
552 pub rc_clone_count: usize,
554 pub arc_clone_count: usize,
556}
557
558impl OwnershipDiagnostics {
559 pub fn has_issues(&self) -> bool {
561 !self.issues.is_empty()
562 }
563
564 pub fn summary(&self) -> String {
566 let mut summary = format!(
567 "Ownership Graph: {} nodes, {} edges\n",
568 self.total_nodes, self.total_edges
569 );
570 for issue in &self.issues {
571 match issue {
572 DiagnosticIssue::RcCycle { nodes, cycle_type } => {
573 summary.push_str(&format!(
574 "🔴 {:?} Cycle detected: {} nodes\n",
575 cycle_type,
576 nodes.len()
577 ));
578 }
579 DiagnosticIssue::ArcCloneStorm {
580 clone_count,
581 threshold,
582 } => {
583 summary.push_str(&format!(
584 "⚠ Arc Clone Storm: {} clones (threshold: {})\n",
585 clone_count, threshold
586 ));
587 }
588 }
589 }
590 summary
591 }
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
596pub enum RootCause {
597 ArcCloneStorm,
599 RcCycle,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize)]
605pub struct RootCauseChain {
606 pub root_cause: RootCause,
608 pub description: String,
610 pub impact: String,
612}
613
614#[cfg(test)]
615mod tests {
616 use super::*;
617
618 #[test]
619 fn test_object_id_from_ptr() {
620 let id = ObjectId::from_ptr(0x1000);
621 assert_eq!(id.0, 0x1000);
622 }
623
624 #[test]
625 fn test_ownership_event_creation() {
626 let id = NodeId(0x1000);
627 let event = OwnershipEvent::new(1000, OwnershipOp::Create, id, None);
628 assert_eq!(event.ts, 1000);
629 assert_eq!(event.op, OwnershipOp::Create);
630 }
631
632 #[test]
633 fn test_graph_build_empty() {
634 let passports: Vec<(NodeId, String, usize, Vec<OwnershipEvent>)> = vec![];
635 let graph = OwnershipGraph::build(&passports);
636 assert!(graph.nodes.is_empty());
637 assert!(graph.edges.is_empty());
638 assert!(graph.cycles.is_empty());
639 }
640
641 #[test]
642 fn test_graph_build_rc_clone() {
643 let id1 = NodeId(0x1000);
644 let id2 = NodeId(0x2000);
645 let events = vec![OwnershipEvent::new(
646 1000,
647 OwnershipOp::RcClone,
648 id1,
649 Some(id2),
650 )];
651 let passports = vec![(id1, "Rc<i32>".to_string(), 8, events)];
652
653 let graph = OwnershipGraph::build(&passports);
654 assert_eq!(graph.nodes.len(), 1);
655 assert_eq!(graph.edges.len(), 1);
656 assert_eq!(graph.edges[0].op, EdgeKind::RcClone);
657 }
658
659 #[test]
660 fn test_graph_build_arc_clone_storm() {
661 let id1 = NodeId(0x1000);
662 let mut events = Vec::new();
663 for i in 0..100 {
664 let dst = NodeId(0x2000 + i);
665 events.push(OwnershipEvent::new(
666 i,
667 OwnershipOp::ArcClone,
668 id1,
669 Some(dst),
670 ));
671 }
672 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
673
674 let graph = OwnershipGraph::build(&passports);
675 assert_eq!(graph.arc_clone_count, 100);
676 assert!(graph.has_arc_clone_storm(50));
677 }
678
679 #[test]
680 fn test_compress_clone_chains() {
681 let id1 = NodeId(0x1000);
682 let id2 = NodeId(0x2000);
683 let id3 = NodeId(0x3000);
684 let id4 = NodeId(0x4000);
685
686 let events = vec![
687 OwnershipEvent::new(1000, OwnershipOp::ArcClone, id1, Some(id2)),
688 OwnershipEvent::new(2000, OwnershipOp::ArcClone, id2, Some(id3)),
689 OwnershipEvent::new(3000, OwnershipOp::ArcClone, id3, Some(id4)),
690 ];
691 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
692
693 let graph = OwnershipGraph::build(&passports);
694
695 assert_eq!(graph.edges.len(), 1);
697 assert_eq!(graph.edges[0].from, id1);
698 assert_eq!(graph.edges[0].to, id4);
699 }
700
701 #[test]
702 fn test_diagnostics() {
703 let id1 = NodeId(0x1000);
704 let mut events = Vec::new();
705 for i in 0..100 {
706 let dst = NodeId(0x2000 + i);
707 events.push(OwnershipEvent::new(
708 i,
709 OwnershipOp::ArcClone,
710 id1,
711 Some(dst),
712 ));
713 }
714 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
715
716 let graph = OwnershipGraph::build(&passports);
717 let diagnostics = graph.diagnostics(50);
718
719 assert!(diagnostics.has_issues());
720 assert!(diagnostics.summary().contains("Arc Clone Storm"));
721 }
722
723 #[test]
724 fn test_root_cause_detection() {
725 let id1 = NodeId(0x1000);
726 let mut events = Vec::new();
727 for i in 0..100 {
728 let dst = NodeId(0x2000 + i);
729 events.push(OwnershipEvent::new(
730 i,
731 OwnershipOp::ArcClone,
732 id1,
733 Some(dst),
734 ));
735 }
736 let passports = vec![(id1, "Arc<i32>".to_string(), 8, events)];
737
738 let graph = OwnershipGraph::build(&passports);
739 let root_cause = graph.find_root_cause();
740
741 assert!(root_cause.is_some());
742 let chain = root_cause.unwrap();
743 assert_eq!(chain.root_cause, RootCause::ArcCloneStorm);
744 }
745
746 #[test]
747 fn test_ownership_op_move() {
748 let id1 = NodeId(0x1000);
749 let id2 = NodeId(0x2000);
750 let events = vec![OwnershipEvent::new(1000, OwnershipOp::Move, id1, Some(id2))];
751 let passports = vec![(id1, "String".to_string(), 24, events)];
752
753 let graph = OwnershipGraph::build(&passports);
754 assert_eq!(graph.edges.len(), 1);
755 assert_eq!(graph.edges[0].op, EdgeKind::Move);
756 }
757
758 #[test]
759 fn test_ownership_op_shared_borrow() {
760 let id1 = NodeId(0x1000);
761 let id2 = NodeId(0x2000);
762 let events = vec![OwnershipEvent::new(
763 1000,
764 OwnershipOp::SharedBorrow,
765 id1,
766 Some(id2),
767 )];
768 let passports = vec![(id1, "String".to_string(), 24, events)];
769
770 let graph = OwnershipGraph::build(&passports);
771 assert_eq!(graph.edges.len(), 1);
772 assert_eq!(graph.edges[0].op, EdgeKind::SharedBorrow);
773 }
774
775 #[test]
776 fn test_ownership_op_mut_borrow() {
777 let id1 = NodeId(0x1000);
778 let id2 = NodeId(0x2000);
779 let events = vec![OwnershipEvent::new(
780 1000,
781 OwnershipOp::MutBorrow,
782 id1,
783 Some(id2),
784 )];
785 let passports = vec![(id1, "String".to_string(), 24, events)];
786
787 let graph = OwnershipGraph::build(&passports);
788 assert_eq!(graph.edges.len(), 1);
789 assert_eq!(graph.edges[0].op, EdgeKind::MutBorrow);
790 }
791
792 #[test]
793 fn test_mixed_ownership_operations() {
794 let id1 = NodeId(0x1000);
795 let id2 = NodeId(0x2000);
796 let id3 = NodeId(0x3000);
797 let events = vec![
798 OwnershipEvent::new(1000, OwnershipOp::Create, id1, None),
799 OwnershipEvent::new(1100, OwnershipOp::Move, id1, Some(id2)),
800 OwnershipEvent::new(1200, OwnershipOp::SharedBorrow, id2, Some(id3)),
801 OwnershipEvent::new(1300, OwnershipOp::Drop, id3, None),
802 ];
803 let passports = vec![(id1, "String".to_string(), 24, events)];
804
805 let graph = OwnershipGraph::build(&passports);
806 assert_eq!(graph.edges.len(), 2);
807 assert_eq!(graph.edges[0].op, EdgeKind::Move);
808 assert_eq!(graph.edges[1].op, EdgeKind::SharedBorrow);
809 }
810}