1use crate::capture::types::{AllocationInfo, TrackingResult};
7use crate::variable_registry::VariableInfo;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, HashSet};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
13pub enum RelationshipType {
14 References,
16 Owns,
18 Clones,
20 Contains,
22 DependsOn,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct VariableNode {
29 pub id: String,
31 pub name: String,
33 pub type_name: String,
35 pub size: usize,
37 pub scope: String,
39 pub is_active: bool,
41 pub category: VariableCategory,
43 pub smart_pointer_info: Option<SmartPointerInfo>,
45 pub created_at: u64,
47 pub destroyed_at: Option<u64>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53pub enum VariableCategory {
54 UserVariable,
56 SystemAllocation,
58 SmartPointer,
60 Collection,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct SmartPointerInfo {
67 pub pointer_type: String,
69 pub ref_count: Option<usize>,
71 pub data_ptr: Option<usize>,
73 pub clones: Vec<usize>,
75 pub cloned_from: Option<usize>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct VariableRelationship {
82 pub source: String,
84 pub target: String,
86 pub relationship_type: RelationshipType,
88 pub weight: f64,
90 pub metadata: HashMap<String, serde_json::Value>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct VariableCluster {
97 pub id: String,
99 pub cluster_type: ClusterType,
101 pub variables: Vec<String>,
103 pub layout_hint: Option<LayoutHint>,
105 pub metadata: HashMap<String, serde_json::Value>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111pub enum ClusterType {
112 Scope,
114 Type,
116 Lifetime,
118 SmartPointerGroup,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct LayoutHint {
125 pub x: f64,
127 pub y: f64,
129 pub width: Option<f64>,
131 pub height: Option<f64>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct VariableRelationshipGraph {
138 pub nodes: Vec<VariableNode>,
140 pub relationships: Vec<VariableRelationship>,
142 pub clusters: Vec<VariableCluster>,
144 pub statistics: GraphStatistics,
146 pub metadata: HashMap<String, serde_json::Value>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct GraphStatistics {
153 pub total_nodes: usize,
155 pub total_relationships: usize,
157 pub circular_references: usize,
159 pub largest_cluster_size: usize,
161 pub isolated_nodes: usize,
163 pub avg_relationships_per_node: f64,
165}
166
167pub struct VariableRelationshipBuilder {
169 nodes: HashMap<String, VariableNode>,
170 relationships: Vec<VariableRelationship>,
171 clusters: Vec<VariableCluster>,
172}
173
174impl VariableRelationshipBuilder {
175 pub fn new() -> Self {
177 Self {
178 nodes: HashMap::new(),
179 relationships: Vec::new(),
180 clusters: Vec::new(),
181 }
182 }
183
184 pub fn add_allocations(
186 mut self,
187 allocations: &[AllocationInfo],
188 registry: &HashMap<usize, VariableInfo>,
189 ) -> Self {
190 for alloc in allocations {
191 let node = self.create_node_from_allocation(alloc, registry);
192 self.nodes.insert(node.id.clone(), node);
193 }
194 self
195 }
196
197 pub fn detect_references(mut self) -> Self {
199 let nodes: Vec<_> = self.nodes.values().cloned().collect();
201
202 for node in &nodes {
203 if let Some(smart_ptr_info) = &node.smart_pointer_info {
204 for &clone_addr in &smart_ptr_info.clones {
206 let clone_id = format!("0x{clone_addr:x}");
207 if self.nodes.contains_key(&clone_id) {
208 self.relationships.push(VariableRelationship {
209 source: node.id.clone(),
210 target: clone_id,
211 relationship_type: RelationshipType::Clones,
212 weight: 1.0,
213 metadata: HashMap::new(),
214 });
215 }
216 }
217
218 if let Some(cloned_from_addr) = smart_ptr_info.cloned_from {
220 let parent_id = format!("0x{cloned_from_addr:x}");
221 if self.nodes.contains_key(&parent_id) {
222 self.relationships.push(VariableRelationship {
223 source: parent_id,
224 target: node.id.clone(),
225 relationship_type: RelationshipType::Clones,
226 weight: 1.0,
227 metadata: HashMap::new(),
228 });
229 }
230 }
231
232 if smart_ptr_info.pointer_type == "Box" {
234 if let Some(data_ptr) = smart_ptr_info.data_ptr {
235 let data_id = format!("0x{data_ptr:x}");
236 if self.nodes.contains_key(&data_id) {
237 self.relationships.push(VariableRelationship {
238 source: node.id.clone(),
239 target: data_id,
240 relationship_type: RelationshipType::Owns,
241 weight: 1.0,
242 metadata: HashMap::new(),
243 });
244 }
245 }
246 }
247 }
248 }
249
250 self
251 }
252
253 pub fn detect_scope_relationships(mut self) -> Self {
255 let mut scope_groups: HashMap<String, Vec<String>> = HashMap::new();
257
258 for node in self.nodes.values() {
259 scope_groups
260 .entry(node.scope.clone())
261 .or_default()
262 .push(node.id.clone());
263 }
264
265 for (scope_name, variable_ids) in scope_groups {
267 if variable_ids.len() > 1 {
268 self.clusters.push(VariableCluster {
270 id: format!("scope_{scope_name}"),
271 cluster_type: ClusterType::Scope,
272 variables: variable_ids.clone(),
273 layout_hint: None,
274 metadata: {
275 let mut meta = HashMap::new();
276 meta.insert(
277 "scope_name".to_string(),
278 serde_json::Value::String(scope_name.clone()),
279 );
280 meta
281 },
282 });
283
284 for i in 0..variable_ids.len() {
286 for j in (i + 1)..variable_ids.len() {
287 self.relationships.push(VariableRelationship {
288 source: variable_ids[i].clone(),
289 target: variable_ids[j].clone(),
290 relationship_type: RelationshipType::Contains,
291 weight: 0.3, metadata: {
293 let mut meta = HashMap::new();
294 meta.insert(
295 "scope".to_string(),
296 serde_json::Value::String(scope_name.clone()),
297 );
298 meta
299 },
300 });
301 }
302 }
303 }
304 }
305
306 self
307 }
308
309 pub fn detect_circular_references(mut self) -> Self {
311 let allocations: Vec<AllocationInfo> = self
313 .nodes
314 .values()
315 .filter_map(|node| self.node_to_allocation_info(node))
316 .collect();
317
318 let circular_analysis =
319 crate::analysis::circular_reference::detect_circular_references(&allocations);
320
321 for cycle in &circular_analysis.circular_references {
323 for window in cycle.cycle_path.windows(2) {
324 if let (Some(source), Some(target)) = (window.first(), window.last()) {
325 let source_id = format!("0x{:x}", source.ptr);
326 let target_id = format!("0x{:x}", target.ptr);
327
328 if self.nodes.contains_key(&source_id) && self.nodes.contains_key(&target_id) {
329 self.relationships.push(VariableRelationship {
330 source: source_id,
331 target: target_id,
332 relationship_type: RelationshipType::DependsOn,
333 weight: 0.8, metadata: {
335 let mut meta = HashMap::new();
336 meta.insert(
337 "circular_reference".to_string(),
338 serde_json::Value::Bool(true),
339 );
340 meta.insert(
341 "cycle_id".to_string(),
342 serde_json::Value::String(format!(
343 "cycle_{}",
344 cycle.cycle_path.len()
345 )),
346 );
347 meta
348 },
349 });
350 }
351 }
352 }
353 }
354
355 self
356 }
357
358 pub fn build_graph(self) -> VariableRelationshipGraph {
360 let statistics = self.calculate_statistics();
361
362 VariableRelationshipGraph {
363 nodes: self.nodes.into_values().collect(),
364 relationships: self.relationships,
365 clusters: self.clusters,
366 statistics,
367 metadata: {
368 let mut meta = HashMap::new();
369 meta.insert(
370 "build_timestamp".to_string(),
371 serde_json::Value::Number(serde_json::Number::from(
372 std::time::SystemTime::now()
373 .duration_since(std::time::UNIX_EPOCH)
374 .unwrap_or_default()
375 .as_secs(),
376 )),
377 );
378 meta
379 },
380 }
381 }
382
383 fn create_node_from_allocation(
385 &self,
386 alloc: &AllocationInfo,
387 registry: &HashMap<usize, VariableInfo>,
388 ) -> VariableNode {
389 let id = format!("0x{:x}", alloc.ptr);
390
391 let (name, type_name, category) = if let Some(var_info) = registry.get(&alloc.ptr) {
393 (
394 var_info.var_name.clone(),
395 var_info.type_name.clone(),
396 VariableCategory::UserVariable,
397 )
398 } else if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
399 (
400 var_name.clone(),
401 type_name.clone(),
402 VariableCategory::UserVariable,
403 )
404 } else {
405 let (inferred_name, inferred_type) =
406 crate::variable_registry::VariableRegistry::infer_allocation_info_cached(alloc);
407 let category = if inferred_type.contains("Vec") || inferred_type.contains("HashMap") {
408 VariableCategory::Collection
409 } else if inferred_type.contains("Box")
410 || inferred_type.contains("Rc")
411 || inferred_type.contains("Arc")
412 {
413 VariableCategory::SmartPointer
414 } else {
415 VariableCategory::SystemAllocation
416 };
417 (inferred_name, inferred_type, category)
418 };
419
420 let scope = alloc
421 .scope_name
422 .as_deref()
423 .unwrap_or_else(|| {
424 if category == VariableCategory::UserVariable {
425 "main"
426 } else {
427 "system"
428 }
429 })
430 .to_string();
431
432 VariableNode {
433 id,
434 name,
435 type_name,
436 size: alloc.size,
437 scope,
438 is_active: alloc.timestamp_dealloc.is_none(),
439 category,
440 smart_pointer_info: alloc
441 .smart_pointer_info
442 .as_ref()
443 .map(|info| SmartPointerInfo {
444 pointer_type: format!("{:?}", info.pointer_type),
445 ref_count: Some(
446 info.ref_count_history
447 .last()
448 .map(|s| s.strong_count)
449 .unwrap_or(0),
450 ),
451 data_ptr: Some(info.data_ptr),
452 clones: info.clones.clone(),
453 cloned_from: info.cloned_from,
454 }),
455 created_at: alloc.timestamp_alloc,
456 destroyed_at: alloc.timestamp_dealloc,
457 }
458 }
459
460 fn node_to_allocation_info(&self, node: &VariableNode) -> Option<AllocationInfo> {
462 let ptr = usize::from_str_radix(&node.id[2..], 16).ok()?;
463
464 let mut alloc = AllocationInfo::new(ptr, node.size);
466
467 if let Some(node_sp_info) = &node.smart_pointer_info {
469 let sp_info = crate::capture::types::smart_pointer::SmartPointerInfo {
470 data_ptr: node_sp_info.data_ptr.unwrap_or(ptr),
471 cloned_from: node_sp_info.cloned_from,
472 clones: node_sp_info.clones.clone(),
473 ref_count_history: if let Some(ref_count) = node_sp_info.ref_count {
474 vec![crate::capture::types::smart_pointer::RefCountSnapshot {
475 timestamp: std::time::SystemTime::now()
476 .duration_since(std::time::UNIX_EPOCH)
477 .unwrap_or_default()
478 .as_nanos() as u64,
479 strong_count: ref_count,
480 weak_count: 0,
481 }]
482 } else {
483 Vec::new()
484 },
485 weak_count: None,
486 is_weak_reference: false,
487 is_data_owner: node_sp_info.ref_count.is_none_or(|c| c > 0),
488 is_implicitly_deallocated: false,
489 pointer_type: match node_sp_info.pointer_type.as_str() {
490 "Rc" => crate::capture::types::smart_pointer::SmartPointerType::Rc,
491 "Arc" => crate::capture::types::smart_pointer::SmartPointerType::Arc,
492 "Box" => crate::capture::types::smart_pointer::SmartPointerType::Box,
493 _ => crate::capture::types::smart_pointer::SmartPointerType::Rc,
494 },
495 };
496 alloc.set_smart_pointer_info(sp_info);
497 }
498
499 Some(alloc)
500 }
501
502 fn calculate_statistics(&self) -> GraphStatistics {
504 let total_nodes = self.nodes.len();
505 let total_relationships = self.relationships.len();
506
507 let circular_references = self
508 .relationships
509 .iter()
510 .filter(|rel| {
511 rel.metadata
512 .get("circular_reference")
513 .and_then(|v| v.as_bool())
514 .unwrap_or(false)
515 })
516 .count();
517
518 let largest_cluster_size = self
519 .clusters
520 .iter()
521 .map(|cluster| cluster.variables.len())
522 .max()
523 .unwrap_or(0);
524
525 let mut connected_nodes = HashSet::new();
527 for rel in &self.relationships {
528 connected_nodes.insert(&rel.source);
529 connected_nodes.insert(&rel.target);
530 }
531 let isolated_nodes = total_nodes - connected_nodes.len();
532
533 let avg_relationships_per_node = if total_nodes > 0 {
534 total_relationships as f64 / total_nodes as f64
535 } else {
536 0.0
537 };
538
539 GraphStatistics {
540 total_nodes,
541 total_relationships,
542 circular_references,
543 largest_cluster_size,
544 isolated_nodes,
545 avg_relationships_per_node,
546 }
547 }
548}
549
550impl Default for VariableRelationshipBuilder {
551 fn default() -> Self {
552 Self::new()
553 }
554}
555
556pub fn build_variable_relationship_graph(
558 allocations: &[AllocationInfo],
559 registry: &HashMap<usize, VariableInfo>,
560) -> TrackingResult<VariableRelationshipGraph> {
561 let graph = VariableRelationshipBuilder::new()
562 .add_allocations(allocations, registry)
563 .detect_references()
564 .detect_scope_relationships()
565 .detect_circular_references()
566 .build_graph();
567
568 Ok(graph)
569}
570
571#[cfg(test)]
572mod tests {
573 use std::thread;
574
575 use super::*;
576 use crate::capture::types::{
577 AllocationInfo, RefCountSnapshot, SmartPointerInfo as CoreSmartPointerInfo,
578 SmartPointerType,
579 };
580 use crate::utils::current_thread_id_u64;
581 use crate::variable_registry::VariableInfo;
582
583 fn create_test_allocation(
585 ptr: usize,
586 size: usize,
587 var_name: Option<String>,
588 type_name: Option<String>,
589 scope_name: Option<String>,
590 ) -> AllocationInfo {
591 let thread_id = thread::current().id();
592 let thread_id_u64 = current_thread_id_u64();
593 AllocationInfo {
594 ptr,
595 size,
596 var_name,
597 type_name,
598 scope_name,
599 timestamp_alloc: 1000,
600 timestamp_dealloc: None,
601 thread_id,
602 thread_id_u64,
603 borrow_count: 0,
604 stack_trace: Some(vec!["test_function".to_string()]),
605 is_leaked: false,
606 lifetime_ms: None,
607 module_path: None,
608 borrow_info: None,
609 clone_info: None,
610 ownership_history_available: false,
611 smart_pointer_info: None,
612 memory_layout: None,
613 generic_info: None,
614 dynamic_type_info: None,
615 runtime_state: None,
616 stack_allocation: None,
617 temporary_object: None,
618 fragmentation_analysis: None,
619 generic_instantiation: None,
620 type_relationships: None,
621 type_usage: None,
622 function_call_tracking: None,
623 lifecycle_tracking: None,
624 access_tracking: None,
625 drop_chain_analysis: None,
626 stack_ptr: None,
627 task_id: None,
628 }
629 }
630
631 #[allow(clippy::too_many_arguments)]
633 fn create_smart_pointer_allocation(
634 ptr: usize,
635 size: usize,
636 var_name: String,
637 type_name: String,
638 pointer_type: SmartPointerType,
639 data_ptr: usize,
640 clones: Vec<usize>,
641 cloned_from: Option<usize>,
642 ) -> AllocationInfo {
643 let mut alloc = create_test_allocation(
644 ptr,
645 size,
646 Some(var_name),
647 Some(type_name),
648 Some("main".to_string()),
649 );
650
651 alloc.smart_pointer_info = Some(CoreSmartPointerInfo {
652 data_ptr,
653 pointer_type,
654 ref_count_history: vec![RefCountSnapshot {
655 timestamp: 1000,
656 strong_count: 1,
657 weak_count: 0,
658 }],
659 weak_count: Some(0),
660 is_data_owner: true,
661 is_weak_reference: false,
662 is_implicitly_deallocated: false,
663 clones,
664 cloned_from,
665 });
666
667 alloc
668 }
669
670 fn create_test_variable_info(var_name: String, type_name: String) -> VariableInfo {
672 VariableInfo {
673 var_name,
674 type_name,
675 timestamp: 1000,
676 size: 64,
677 thread_id: 1,
678 memory_usage: 64,
679 }
680 }
681
682 #[test]
683 fn test_relationship_type_serialization() {
684 let relationship_types = vec![
685 RelationshipType::References,
686 RelationshipType::Owns,
687 RelationshipType::Clones,
688 RelationshipType::Contains,
689 RelationshipType::DependsOn,
690 ];
691
692 for rel_type in relationship_types {
693 let serialized = serde_json::to_string(&rel_type).expect("Failed to serialize");
694 let _deserialized: RelationshipType =
695 serde_json::from_str(&serialized).expect("Failed to deserialize");
696 }
697 }
698
699 #[test]
700 fn test_variable_category_serialization() {
701 let categories = vec![
702 VariableCategory::UserVariable,
703 VariableCategory::SystemAllocation,
704 VariableCategory::SmartPointer,
705 VariableCategory::Collection,
706 ];
707
708 for category in categories {
709 let serialized = serde_json::to_string(&category).expect("Failed to serialize");
710 let _deserialized: VariableCategory =
711 serde_json::from_str(&serialized).expect("Failed to deserialize");
712 }
713 }
714
715 #[test]
716 fn test_cluster_type_serialization() {
717 let cluster_types = vec![
718 ClusterType::Scope,
719 ClusterType::Type,
720 ClusterType::Lifetime,
721 ClusterType::SmartPointerGroup,
722 ];
723
724 for cluster_type in cluster_types {
725 let serialized = serde_json::to_string(&cluster_type).expect("Failed to serialize");
726 let _deserialized: ClusterType =
727 serde_json::from_str(&serialized).expect("Failed to deserialize");
728 }
729 }
730
731 #[test]
732 fn test_variable_relationship_builder_creation() {
733 let builder = VariableRelationshipBuilder::new();
734 assert!(builder.nodes.is_empty());
735 assert!(builder.relationships.is_empty());
736 assert!(builder.clusters.is_empty());
737 }
738
739 #[test]
740 fn test_variable_relationship_builder_default() {
741 let builder = VariableRelationshipBuilder::default();
742 assert!(builder.nodes.is_empty());
743 assert!(builder.relationships.is_empty());
744 assert!(builder.clusters.is_empty());
745 }
746
747 #[test]
748 fn test_add_allocations_basic() {
749 let allocations = vec![
750 create_test_allocation(
751 0x1000,
752 1024,
753 Some("var1".to_string()),
754 Some("i32".to_string()),
755 Some("main".to_string()),
756 ),
757 create_test_allocation(
758 0x2000,
759 512,
760 Some("var2".to_string()),
761 Some("String".to_string()),
762 Some("main".to_string()),
763 ),
764 ];
765
766 let registry = HashMap::new();
767
768 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
769
770 assert_eq!(builder.nodes.len(), 2);
771 assert!(builder.nodes.contains_key("0x1000"));
772 assert!(builder.nodes.contains_key("0x2000"));
773
774 let node1 = &builder.nodes["0x1000"];
775 assert_eq!(node1.name, "var1");
776 assert_eq!(node1.type_name, "i32");
777 assert_eq!(node1.size, 1024);
778 assert_eq!(node1.scope, "main");
779 assert!(node1.is_active);
780 assert_eq!(node1.category, VariableCategory::UserVariable);
781 }
782
783 #[test]
784 fn test_add_allocations_with_registry() {
785 let allocations = vec![create_test_allocation(0x1000, 1024, None, None, None)];
786
787 let mut registry = HashMap::new();
788 registry.insert(
789 0x1000,
790 create_test_variable_info("registry_var".to_string(), "u64".to_string()),
791 );
792
793 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
794
795 assert_eq!(builder.nodes.len(), 1);
796 let node = &builder.nodes["0x1000"];
797 assert_eq!(node.name, "registry_var");
798 assert_eq!(node.type_name, "u64");
799 assert_eq!(node.category, VariableCategory::UserVariable);
800 }
801
802 #[test]
803 fn test_add_allocations_inferred_categories() {
804 let allocations = vec![
805 create_test_allocation(0x1000, 1024, None, Some("Vec<i32>".to_string()), None),
806 create_test_allocation(0x2000, 512, None, Some("Box<String>".to_string()), None),
807 create_test_allocation(
808 0x3000,
809 256,
810 None,
811 Some("HashMap<String, i32>".to_string()),
812 None,
813 ),
814 create_test_allocation(0x4000, 128, None, Some("Rc<Data>".to_string()), None),
815 ];
816
817 let registry = HashMap::new();
818
819 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
820
821 assert_eq!(builder.nodes.len(), 4);
822
823 let categories: Vec<_> = builder.nodes.values().map(|node| &node.category).collect();
826
827 let has_user_vars = categories
829 .iter()
830 .any(|&cat| *cat == VariableCategory::UserVariable);
831 let has_smart_ptrs = categories
832 .iter()
833 .any(|&cat| *cat == VariableCategory::SmartPointer);
834 let has_collections = categories
835 .iter()
836 .any(|&cat| *cat == VariableCategory::Collection);
837 let has_system_allocs = categories
838 .iter()
839 .any(|&cat| *cat == VariableCategory::SystemAllocation);
840
841 assert!(has_user_vars || has_smart_ptrs || has_collections || has_system_allocs);
843
844 assert!(builder.nodes.contains_key("0x1000"));
846 assert!(builder.nodes.contains_key("0x2000"));
847 assert!(builder.nodes.contains_key("0x3000"));
848 assert!(builder.nodes.contains_key("0x4000"));
849 }
850
851 #[test]
852 fn test_detect_references_smart_pointers() {
853 let allocations = vec![
854 create_smart_pointer_allocation(
855 0x1000,
856 64,
857 "rc1".to_string(),
858 "Rc<i32>".to_string(),
859 SmartPointerType::Rc,
860 0x5000,
861 vec![0x2000, 0x3000],
862 None,
863 ),
864 create_smart_pointer_allocation(
865 0x2000,
866 64,
867 "rc2".to_string(),
868 "Rc<i32>".to_string(),
869 SmartPointerType::Rc,
870 0x5000,
871 vec![],
872 Some(0x1000),
873 ),
874 create_smart_pointer_allocation(
875 0x3000,
876 64,
877 "rc3".to_string(),
878 "Rc<i32>".to_string(),
879 SmartPointerType::Rc,
880 0x5000,
881 vec![],
882 Some(0x1000),
883 ),
884 ];
885
886 let registry = HashMap::new();
887
888 let builder = VariableRelationshipBuilder::new()
889 .add_allocations(&allocations, ®istry)
890 .detect_references();
891
892 assert!(!builder.relationships.is_empty());
894
895 let clone_relationships: Vec<_> = builder
896 .relationships
897 .iter()
898 .filter(|rel| rel.relationship_type == RelationshipType::Clones)
899 .collect();
900
901 assert!(!clone_relationships.is_empty());
902
903 let has_clone_rel = clone_relationships.iter().any(|rel| {
905 rel.source == "0x1000" && (rel.target == "0x2000" || rel.target == "0x3000")
906 });
907
908 assert!(has_clone_rel);
909 }
910
911 #[test]
912 fn test_detect_references_box_ownership() {
913 let allocations = vec![
914 create_smart_pointer_allocation(
915 0x1000,
916 64,
917 "box_ptr".to_string(),
918 "Box<String>".to_string(),
919 SmartPointerType::Box,
920 0x2000,
921 vec![],
922 None,
923 ),
924 create_test_allocation(
925 0x2000,
926 32,
927 Some("data".to_string()),
928 Some("String".to_string()),
929 Some("main".to_string()),
930 ),
931 ];
932
933 let registry = HashMap::new();
934
935 let builder = VariableRelationshipBuilder::new()
936 .add_allocations(&allocations, ®istry)
937 .detect_references();
938
939 let ownership_relationships: Vec<_> = builder
941 .relationships
942 .iter()
943 .filter(|rel| rel.relationship_type == RelationshipType::Owns)
944 .collect();
945
946 assert!(!ownership_relationships.is_empty());
947
948 let has_ownership = ownership_relationships
949 .iter()
950 .any(|rel| rel.source == "0x1000" && rel.target == "0x2000");
951
952 assert!(has_ownership);
953 }
954
955 #[test]
956 fn test_detect_scope_relationships() {
957 let allocations = vec![
958 create_test_allocation(
959 0x1000,
960 1024,
961 Some("var1".to_string()),
962 Some("i32".to_string()),
963 Some("main".to_string()),
964 ),
965 create_test_allocation(
966 0x2000,
967 512,
968 Some("var2".to_string()),
969 Some("String".to_string()),
970 Some("main".to_string()),
971 ),
972 create_test_allocation(
973 0x3000,
974 256,
975 Some("var3".to_string()),
976 Some("f64".to_string()),
977 Some("function".to_string()),
978 ),
979 ];
980
981 let registry = HashMap::new();
982
983 let builder = VariableRelationshipBuilder::new()
984 .add_allocations(&allocations, ®istry)
985 .detect_scope_relationships();
986
987 assert!(!builder.clusters.is_empty());
989
990 let scope_clusters: Vec<_> = builder
991 .clusters
992 .iter()
993 .filter(|cluster| cluster.cluster_type == ClusterType::Scope)
994 .collect();
995
996 assert!(!scope_clusters.is_empty());
997
998 let main_cluster = scope_clusters
1000 .iter()
1001 .find(|cluster| cluster.id == "scope_main");
1002
1003 assert!(main_cluster.is_some());
1004 let main_cluster = main_cluster.unwrap();
1005 assert_eq!(main_cluster.variables.len(), 2);
1006 assert!(main_cluster.variables.contains(&"0x1000".to_string()));
1007 assert!(main_cluster.variables.contains(&"0x2000".to_string()));
1008
1009 let containment_relationships: Vec<_> = builder
1011 .relationships
1012 .iter()
1013 .filter(|rel| rel.relationship_type == RelationshipType::Contains)
1014 .collect();
1015
1016 assert!(!containment_relationships.is_empty());
1017 }
1018
1019 #[test]
1020 fn test_build_graph_basic() {
1021 let allocations = vec![
1022 create_test_allocation(
1023 0x1000,
1024 1024,
1025 Some("var1".to_string()),
1026 Some("i32".to_string()),
1027 Some("main".to_string()),
1028 ),
1029 create_test_allocation(
1030 0x2000,
1031 512,
1032 Some("var2".to_string()),
1033 Some("String".to_string()),
1034 Some("main".to_string()),
1035 ),
1036 ];
1037
1038 let registry = HashMap::new();
1039
1040 let graph = VariableRelationshipBuilder::new()
1041 .add_allocations(&allocations, ®istry)
1042 .detect_scope_relationships()
1043 .build_graph();
1044
1045 assert_eq!(graph.nodes.len(), 2);
1046 assert!(!graph.relationships.is_empty());
1047 assert!(!graph.clusters.is_empty());
1048
1049 assert_eq!(graph.statistics.total_nodes, 2);
1051 assert!(graph.statistics.total_relationships > 0);
1052 assert!(graph.statistics.avg_relationships_per_node >= 0.0);
1053
1054 assert!(graph.metadata.contains_key("build_timestamp"));
1056 }
1057
1058 #[test]
1059 fn test_calculate_statistics() {
1060 let allocations = vec![
1061 create_test_allocation(
1062 0x1000,
1063 1024,
1064 Some("var1".to_string()),
1065 Some("i32".to_string()),
1066 Some("main".to_string()),
1067 ),
1068 create_test_allocation(
1069 0x2000,
1070 512,
1071 Some("var2".to_string()),
1072 Some("String".to_string()),
1073 Some("main".to_string()),
1074 ),
1075 create_test_allocation(
1076 0x3000,
1077 256,
1078 Some("isolated".to_string()),
1079 Some("f64".to_string()),
1080 Some("other".to_string()),
1081 ),
1082 ];
1083
1084 let registry = HashMap::new();
1085
1086 let graph = VariableRelationshipBuilder::new()
1087 .add_allocations(&allocations, ®istry)
1088 .detect_scope_relationships()
1089 .build_graph();
1090
1091 let stats = &graph.statistics;
1092 assert_eq!(stats.total_nodes, 3);
1093 assert!(stats.total_relationships > 0);
1094 assert!(stats.largest_cluster_size >= 2); assert!(stats.isolated_nodes <= 1); assert!(stats.avg_relationships_per_node >= 0.0);
1097 }
1098
1099 #[test]
1100 fn test_smart_pointer_info_conversion() {
1101 let allocations = vec![create_smart_pointer_allocation(
1102 0x1000,
1103 64,
1104 "rc_ptr".to_string(),
1105 "Rc<Data>".to_string(),
1106 SmartPointerType::Rc,
1107 0x2000,
1108 vec![0x3000],
1109 Some(0x4000),
1110 )];
1111
1112 let registry = HashMap::new();
1113
1114 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
1115
1116 let node = &builder.nodes["0x1000"];
1117 assert!(node.smart_pointer_info.is_some());
1118
1119 let smart_ptr_info = node.smart_pointer_info.as_ref().unwrap();
1120 assert_eq!(smart_ptr_info.pointer_type, "Rc");
1121 assert_eq!(smart_ptr_info.ref_count, Some(1));
1122 assert_eq!(smart_ptr_info.data_ptr, Some(0x2000));
1123 assert_eq!(smart_ptr_info.clones, vec![0x3000]);
1124 assert_eq!(smart_ptr_info.cloned_from, Some(0x4000));
1125 }
1126
1127 #[test]
1128 fn test_layout_hint_serialization() {
1129 let layout_hint = LayoutHint {
1130 x: 10.0,
1131 y: 20.0,
1132 width: Some(100.0),
1133 height: Some(50.0),
1134 };
1135
1136 let serialized = serde_json::to_string(&layout_hint).expect("Failed to serialize");
1137 let deserialized: LayoutHint =
1138 serde_json::from_str(&serialized).expect("Failed to deserialize");
1139
1140 assert_eq!(deserialized.x, 10.0);
1141 assert_eq!(deserialized.y, 20.0);
1142 assert_eq!(deserialized.width, Some(100.0));
1143 assert_eq!(deserialized.height, Some(50.0));
1144 }
1145
1146 #[test]
1147 fn test_variable_node_serialization() {
1148 let node = VariableNode {
1149 id: "0x1000".to_string(),
1150 name: "test_var".to_string(),
1151 type_name: "i32".to_string(),
1152 size: 4,
1153 scope: "main".to_string(),
1154 is_active: true,
1155 category: VariableCategory::UserVariable,
1156 smart_pointer_info: None,
1157 created_at: 1000,
1158 destroyed_at: None,
1159 };
1160
1161 let serialized = serde_json::to_string(&node).expect("Failed to serialize");
1162 let deserialized: VariableNode =
1163 serde_json::from_str(&serialized).expect("Failed to deserialize");
1164
1165 assert_eq!(deserialized.id, "0x1000");
1166 assert_eq!(deserialized.name, "test_var");
1167 assert_eq!(deserialized.type_name, "i32");
1168 assert_eq!(deserialized.size, 4);
1169 assert!(deserialized.is_active);
1170 }
1171
1172 #[test]
1173 fn test_variable_relationship_serialization() {
1174 let mut metadata = HashMap::new();
1175 metadata.insert(
1176 "test_key".to_string(),
1177 serde_json::Value::String("test_value".to_string()),
1178 );
1179
1180 let relationship = VariableRelationship {
1181 source: "0x1000".to_string(),
1182 target: "0x2000".to_string(),
1183 relationship_type: RelationshipType::References,
1184 weight: 0.8,
1185 metadata,
1186 };
1187
1188 let serialized = serde_json::to_string(&relationship).expect("Failed to serialize");
1189 let deserialized: VariableRelationship =
1190 serde_json::from_str(&serialized).expect("Failed to deserialize");
1191
1192 assert_eq!(deserialized.source, "0x1000");
1193 assert_eq!(deserialized.target, "0x2000");
1194 assert_eq!(deserialized.relationship_type, RelationshipType::References);
1195 assert_eq!(deserialized.weight, 0.8);
1196 assert!(deserialized.metadata.contains_key("test_key"));
1197 }
1198
1199 #[test]
1200 fn test_build_variable_relationship_graph_function() {
1201 let allocations = vec![
1202 create_test_allocation(
1203 0x1000,
1204 1024,
1205 Some("var1".to_string()),
1206 Some("i32".to_string()),
1207 Some("main".to_string()),
1208 ),
1209 create_test_allocation(
1210 0x2000,
1211 512,
1212 Some("var2".to_string()),
1213 Some("String".to_string()),
1214 Some("main".to_string()),
1215 ),
1216 ];
1217
1218 let registry = HashMap::new();
1219
1220 let result = build_variable_relationship_graph(&allocations, ®istry);
1221 assert!(result.is_ok());
1222
1223 let graph = result.unwrap();
1224 assert_eq!(graph.nodes.len(), 2);
1225 assert!(!graph.relationships.is_empty());
1226 assert!(!graph.clusters.is_empty());
1227 }
1228
1229 #[test]
1230 fn test_comprehensive_workflow() {
1231 let allocations = vec![
1233 create_test_allocation(
1235 0x1000,
1236 1024,
1237 Some("main_var1".to_string()),
1238 Some("i32".to_string()),
1239 Some("main".to_string()),
1240 ),
1241 create_test_allocation(
1242 0x2000,
1243 512,
1244 Some("main_var2".to_string()),
1245 Some("String".to_string()),
1246 Some("main".to_string()),
1247 ),
1248 create_test_allocation(
1250 0x3000,
1251 256,
1252 Some("func_var".to_string()),
1253 Some("f64".to_string()),
1254 Some("function".to_string()),
1255 ),
1256 create_smart_pointer_allocation(
1258 0x4000,
1259 64,
1260 "rc1".to_string(),
1261 "Rc<Data>".to_string(),
1262 SmartPointerType::Rc,
1263 0x6000,
1264 vec![0x5000],
1265 None,
1266 ),
1267 create_smart_pointer_allocation(
1268 0x5000,
1269 64,
1270 "rc2".to_string(),
1271 "Rc<Data>".to_string(),
1272 SmartPointerType::Rc,
1273 0x6000,
1274 vec![],
1275 Some(0x4000),
1276 ),
1277 create_smart_pointer_allocation(
1279 0x7000,
1280 64,
1281 "box_ptr".to_string(),
1282 "Box<String>".to_string(),
1283 SmartPointerType::Box,
1284 0x8000,
1285 vec![],
1286 None,
1287 ),
1288 create_test_allocation(
1289 0x8000,
1290 32,
1291 Some("boxed_data".to_string()),
1292 Some("String".to_string()),
1293 Some("main".to_string()),
1294 ),
1295 create_test_allocation(
1297 0x9000,
1298 128,
1299 Some("vec_data".to_string()),
1300 Some("Vec<i32>".to_string()),
1301 Some("main".to_string()),
1302 ),
1303 ];
1304
1305 let mut registry = HashMap::new();
1306 registry.insert(
1307 0x1000,
1308 create_test_variable_info("registry_main_var".to_string(), "u32".to_string()),
1309 );
1310
1311 let graph = VariableRelationshipBuilder::new()
1312 .add_allocations(&allocations, ®istry)
1313 .detect_references()
1314 .detect_scope_relationships()
1315 .detect_circular_references()
1316 .build_graph();
1317
1318 assert_eq!(graph.nodes.len(), 8);
1320 assert!(!graph.relationships.is_empty());
1321 assert!(!graph.clusters.is_empty());
1322
1323 let has_clones = graph
1325 .relationships
1326 .iter()
1327 .any(|rel| rel.relationship_type == RelationshipType::Clones);
1328 let has_ownership = graph
1329 .relationships
1330 .iter()
1331 .any(|rel| rel.relationship_type == RelationshipType::Owns);
1332 let has_containment = graph
1333 .relationships
1334 .iter()
1335 .any(|rel| rel.relationship_type == RelationshipType::Contains);
1336
1337 assert!(has_clones || has_ownership || has_containment);
1338
1339 let mut categories = Vec::new();
1341
1342 for node in &graph.nodes {
1344 if !categories.contains(&&node.category) {
1345 categories.push(&node.category);
1346 }
1347 }
1348
1349 assert!(
1351 !categories.is_empty(),
1352 "Expected at least one variable category"
1353 );
1354
1355 let has_user_vars = categories.contains(&&VariableCategory::UserVariable);
1357 assert!(has_user_vars, "Expected at least one user variable");
1358
1359 let stats = &graph.statistics;
1361 assert_eq!(stats.total_nodes, 8);
1362 assert!(stats.total_relationships > 0);
1363 assert!(stats.avg_relationships_per_node >= 0.0);
1364
1365 assert!(graph.metadata.contains_key("build_timestamp"));
1367
1368 let scope_clusters: Vec<_> = graph
1370 .clusters
1371 .iter()
1372 .filter(|cluster| cluster.cluster_type == ClusterType::Scope)
1373 .collect();
1374
1375 assert!(!scope_clusters.is_empty());
1376
1377 let main_cluster = scope_clusters
1379 .iter()
1380 .find(|cluster| cluster.variables.len() > 1);
1381
1382 assert!(main_cluster.is_some());
1383 }
1384
1385 #[test]
1386 fn test_circular_reference_relationships_use_ptr_not_stack_address() {
1387 let allocations = vec![
1388 create_smart_pointer_allocation(
1389 0x1000,
1390 64,
1391 "rc1".to_string(),
1392 "Rc<Data>".to_string(),
1393 SmartPointerType::Rc,
1394 0x5000,
1395 vec![0x2000],
1396 None,
1397 ),
1398 create_smart_pointer_allocation(
1399 0x2000,
1400 64,
1401 "rc2".to_string(),
1402 "Rc<Data>".to_string(),
1403 SmartPointerType::Rc,
1404 0x5000,
1405 vec![],
1406 Some(0x1000),
1407 ),
1408 ];
1409
1410 let registry = HashMap::new();
1411
1412 let graph = VariableRelationshipBuilder::new()
1413 .add_allocations(&allocations, ®istry)
1414 .detect_references()
1415 .detect_circular_references()
1416 .build_graph();
1417
1418 let circular_rels: Vec<_> = graph
1419 .relationships
1420 .iter()
1421 .filter(|rel| {
1422 rel.metadata
1423 .get("circular_reference")
1424 .and_then(|v| v.as_bool())
1425 .unwrap_or(false)
1426 })
1427 .collect();
1428
1429 for rel in &circular_rels {
1430 assert!(
1431 rel.source.starts_with("0x"),
1432 "Circular reference source should use pointer format '0x...', got: {}",
1433 rel.source
1434 );
1435 assert!(
1436 rel.target.starts_with("0x"),
1437 "Circular reference target should use pointer format '0x...', got: {}",
1438 rel.target
1439 );
1440 let source_addr: usize =
1441 usize::from_str_radix(rel.source.trim_start_matches("0x"), 16).unwrap();
1442 let target_addr: usize =
1443 usize::from_str_radix(rel.target.trim_start_matches("0x"), 16).unwrap();
1444 assert!(
1445 source_addr == 0x1000 || source_addr == 0x2000,
1446 "Circular reference source should be 0x1000 or 0x2000, got: {:#x}",
1447 source_addr
1448 );
1449 assert!(
1450 target_addr == 0x1000 || target_addr == 0x2000,
1451 "Circular reference target should be 0x1000 or 0x2000, got: {:#x}",
1452 target_addr
1453 );
1454 }
1455 }
1456}