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 borrow_info: None,
608 clone_info: None,
609 ownership_history_available: false,
610 smart_pointer_info: None,
611 memory_layout: None,
612 generic_info: None,
613 dynamic_type_info: None,
614 runtime_state: None,
615 stack_allocation: None,
616 temporary_object: None,
617 fragmentation_analysis: None,
618 generic_instantiation: None,
619 type_relationships: None,
620 type_usage: None,
621 function_call_tracking: None,
622 lifecycle_tracking: None,
623 access_tracking: None,
624 drop_chain_analysis: None,
625 }
626 }
627
628 #[allow(clippy::too_many_arguments)]
630 fn create_smart_pointer_allocation(
631 ptr: usize,
632 size: usize,
633 var_name: String,
634 type_name: String,
635 pointer_type: SmartPointerType,
636 data_ptr: usize,
637 clones: Vec<usize>,
638 cloned_from: Option<usize>,
639 ) -> AllocationInfo {
640 let mut alloc = create_test_allocation(
641 ptr,
642 size,
643 Some(var_name),
644 Some(type_name),
645 Some("main".to_string()),
646 );
647
648 alloc.smart_pointer_info = Some(CoreSmartPointerInfo {
649 data_ptr,
650 pointer_type,
651 ref_count_history: vec![RefCountSnapshot {
652 timestamp: 1000,
653 strong_count: 1,
654 weak_count: 0,
655 }],
656 weak_count: Some(0),
657 is_data_owner: true,
658 is_weak_reference: false,
659 is_implicitly_deallocated: false,
660 clones,
661 cloned_from,
662 });
663
664 alloc
665 }
666
667 fn create_test_variable_info(var_name: String, type_name: String) -> VariableInfo {
669 VariableInfo {
670 var_name,
671 type_name,
672 timestamp: 1000,
673 size: 64,
674 thread_id: 1,
675 memory_usage: 64,
676 }
677 }
678
679 #[test]
680 fn test_relationship_type_serialization() {
681 let relationship_types = vec![
682 RelationshipType::References,
683 RelationshipType::Owns,
684 RelationshipType::Clones,
685 RelationshipType::Contains,
686 RelationshipType::DependsOn,
687 ];
688
689 for rel_type in relationship_types {
690 let serialized = serde_json::to_string(&rel_type).expect("Failed to serialize");
691 let _deserialized: RelationshipType =
692 serde_json::from_str(&serialized).expect("Failed to deserialize");
693 }
694 }
695
696 #[test]
697 fn test_variable_category_serialization() {
698 let categories = vec![
699 VariableCategory::UserVariable,
700 VariableCategory::SystemAllocation,
701 VariableCategory::SmartPointer,
702 VariableCategory::Collection,
703 ];
704
705 for category in categories {
706 let serialized = serde_json::to_string(&category).expect("Failed to serialize");
707 let _deserialized: VariableCategory =
708 serde_json::from_str(&serialized).expect("Failed to deserialize");
709 }
710 }
711
712 #[test]
713 fn test_cluster_type_serialization() {
714 let cluster_types = vec![
715 ClusterType::Scope,
716 ClusterType::Type,
717 ClusterType::Lifetime,
718 ClusterType::SmartPointerGroup,
719 ];
720
721 for cluster_type in cluster_types {
722 let serialized = serde_json::to_string(&cluster_type).expect("Failed to serialize");
723 let _deserialized: ClusterType =
724 serde_json::from_str(&serialized).expect("Failed to deserialize");
725 }
726 }
727
728 #[test]
729 fn test_variable_relationship_builder_creation() {
730 let builder = VariableRelationshipBuilder::new();
731 assert!(builder.nodes.is_empty());
732 assert!(builder.relationships.is_empty());
733 assert!(builder.clusters.is_empty());
734 }
735
736 #[test]
737 fn test_variable_relationship_builder_default() {
738 let builder = VariableRelationshipBuilder::default();
739 assert!(builder.nodes.is_empty());
740 assert!(builder.relationships.is_empty());
741 assert!(builder.clusters.is_empty());
742 }
743
744 #[test]
745 fn test_add_allocations_basic() {
746 let allocations = vec![
747 create_test_allocation(
748 0x1000,
749 1024,
750 Some("var1".to_string()),
751 Some("i32".to_string()),
752 Some("main".to_string()),
753 ),
754 create_test_allocation(
755 0x2000,
756 512,
757 Some("var2".to_string()),
758 Some("String".to_string()),
759 Some("main".to_string()),
760 ),
761 ];
762
763 let registry = HashMap::new();
764
765 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
766
767 assert_eq!(builder.nodes.len(), 2);
768 assert!(builder.nodes.contains_key("0x1000"));
769 assert!(builder.nodes.contains_key("0x2000"));
770
771 let node1 = &builder.nodes["0x1000"];
772 assert_eq!(node1.name, "var1");
773 assert_eq!(node1.type_name, "i32");
774 assert_eq!(node1.size, 1024);
775 assert_eq!(node1.scope, "main");
776 assert!(node1.is_active);
777 assert_eq!(node1.category, VariableCategory::UserVariable);
778 }
779
780 #[test]
781 fn test_add_allocations_with_registry() {
782 let allocations = vec![create_test_allocation(0x1000, 1024, None, None, None)];
783
784 let mut registry = HashMap::new();
785 registry.insert(
786 0x1000,
787 create_test_variable_info("registry_var".to_string(), "u64".to_string()),
788 );
789
790 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
791
792 assert_eq!(builder.nodes.len(), 1);
793 let node = &builder.nodes["0x1000"];
794 assert_eq!(node.name, "registry_var");
795 assert_eq!(node.type_name, "u64");
796 assert_eq!(node.category, VariableCategory::UserVariable);
797 }
798
799 #[test]
800 fn test_add_allocations_inferred_categories() {
801 let allocations = vec![
802 create_test_allocation(0x1000, 1024, None, Some("Vec<i32>".to_string()), None),
803 create_test_allocation(0x2000, 512, None, Some("Box<String>".to_string()), None),
804 create_test_allocation(
805 0x3000,
806 256,
807 None,
808 Some("HashMap<String, i32>".to_string()),
809 None,
810 ),
811 create_test_allocation(0x4000, 128, None, Some("Rc<Data>".to_string()), None),
812 ];
813
814 let registry = HashMap::new();
815
816 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
817
818 assert_eq!(builder.nodes.len(), 4);
819
820 let categories: Vec<_> = builder.nodes.values().map(|node| &node.category).collect();
823
824 let has_user_vars = categories
826 .iter()
827 .any(|&cat| *cat == VariableCategory::UserVariable);
828 let has_smart_ptrs = categories
829 .iter()
830 .any(|&cat| *cat == VariableCategory::SmartPointer);
831 let has_collections = categories
832 .iter()
833 .any(|&cat| *cat == VariableCategory::Collection);
834 let has_system_allocs = categories
835 .iter()
836 .any(|&cat| *cat == VariableCategory::SystemAllocation);
837
838 assert!(has_user_vars || has_smart_ptrs || has_collections || has_system_allocs);
840
841 assert!(builder.nodes.contains_key("0x1000"));
843 assert!(builder.nodes.contains_key("0x2000"));
844 assert!(builder.nodes.contains_key("0x3000"));
845 assert!(builder.nodes.contains_key("0x4000"));
846 }
847
848 #[test]
849 fn test_detect_references_smart_pointers() {
850 let allocations = vec![
851 create_smart_pointer_allocation(
852 0x1000,
853 64,
854 "rc1".to_string(),
855 "Rc<i32>".to_string(),
856 SmartPointerType::Rc,
857 0x5000,
858 vec![0x2000, 0x3000],
859 None,
860 ),
861 create_smart_pointer_allocation(
862 0x2000,
863 64,
864 "rc2".to_string(),
865 "Rc<i32>".to_string(),
866 SmartPointerType::Rc,
867 0x5000,
868 vec![],
869 Some(0x1000),
870 ),
871 create_smart_pointer_allocation(
872 0x3000,
873 64,
874 "rc3".to_string(),
875 "Rc<i32>".to_string(),
876 SmartPointerType::Rc,
877 0x5000,
878 vec![],
879 Some(0x1000),
880 ),
881 ];
882
883 let registry = HashMap::new();
884
885 let builder = VariableRelationshipBuilder::new()
886 .add_allocations(&allocations, ®istry)
887 .detect_references();
888
889 assert!(!builder.relationships.is_empty());
891
892 let clone_relationships: Vec<_> = builder
893 .relationships
894 .iter()
895 .filter(|rel| rel.relationship_type == RelationshipType::Clones)
896 .collect();
897
898 assert!(!clone_relationships.is_empty());
899
900 let has_clone_rel = clone_relationships.iter().any(|rel| {
902 rel.source == "0x1000" && (rel.target == "0x2000" || rel.target == "0x3000")
903 });
904
905 assert!(has_clone_rel);
906 }
907
908 #[test]
909 fn test_detect_references_box_ownership() {
910 let allocations = vec![
911 create_smart_pointer_allocation(
912 0x1000,
913 64,
914 "box_ptr".to_string(),
915 "Box<String>".to_string(),
916 SmartPointerType::Box,
917 0x2000,
918 vec![],
919 None,
920 ),
921 create_test_allocation(
922 0x2000,
923 32,
924 Some("data".to_string()),
925 Some("String".to_string()),
926 Some("main".to_string()),
927 ),
928 ];
929
930 let registry = HashMap::new();
931
932 let builder = VariableRelationshipBuilder::new()
933 .add_allocations(&allocations, ®istry)
934 .detect_references();
935
936 let ownership_relationships: Vec<_> = builder
938 .relationships
939 .iter()
940 .filter(|rel| rel.relationship_type == RelationshipType::Owns)
941 .collect();
942
943 assert!(!ownership_relationships.is_empty());
944
945 let has_ownership = ownership_relationships
946 .iter()
947 .any(|rel| rel.source == "0x1000" && rel.target == "0x2000");
948
949 assert!(has_ownership);
950 }
951
952 #[test]
953 fn test_detect_scope_relationships() {
954 let allocations = vec![
955 create_test_allocation(
956 0x1000,
957 1024,
958 Some("var1".to_string()),
959 Some("i32".to_string()),
960 Some("main".to_string()),
961 ),
962 create_test_allocation(
963 0x2000,
964 512,
965 Some("var2".to_string()),
966 Some("String".to_string()),
967 Some("main".to_string()),
968 ),
969 create_test_allocation(
970 0x3000,
971 256,
972 Some("var3".to_string()),
973 Some("f64".to_string()),
974 Some("function".to_string()),
975 ),
976 ];
977
978 let registry = HashMap::new();
979
980 let builder = VariableRelationshipBuilder::new()
981 .add_allocations(&allocations, ®istry)
982 .detect_scope_relationships();
983
984 assert!(!builder.clusters.is_empty());
986
987 let scope_clusters: Vec<_> = builder
988 .clusters
989 .iter()
990 .filter(|cluster| cluster.cluster_type == ClusterType::Scope)
991 .collect();
992
993 assert!(!scope_clusters.is_empty());
994
995 let main_cluster = scope_clusters
997 .iter()
998 .find(|cluster| cluster.id == "scope_main");
999
1000 assert!(main_cluster.is_some());
1001 let main_cluster = main_cluster.unwrap();
1002 assert_eq!(main_cluster.variables.len(), 2);
1003 assert!(main_cluster.variables.contains(&"0x1000".to_string()));
1004 assert!(main_cluster.variables.contains(&"0x2000".to_string()));
1005
1006 let containment_relationships: Vec<_> = builder
1008 .relationships
1009 .iter()
1010 .filter(|rel| rel.relationship_type == RelationshipType::Contains)
1011 .collect();
1012
1013 assert!(!containment_relationships.is_empty());
1014 }
1015
1016 #[test]
1017 fn test_build_graph_basic() {
1018 let allocations = vec![
1019 create_test_allocation(
1020 0x1000,
1021 1024,
1022 Some("var1".to_string()),
1023 Some("i32".to_string()),
1024 Some("main".to_string()),
1025 ),
1026 create_test_allocation(
1027 0x2000,
1028 512,
1029 Some("var2".to_string()),
1030 Some("String".to_string()),
1031 Some("main".to_string()),
1032 ),
1033 ];
1034
1035 let registry = HashMap::new();
1036
1037 let graph = VariableRelationshipBuilder::new()
1038 .add_allocations(&allocations, ®istry)
1039 .detect_scope_relationships()
1040 .build_graph();
1041
1042 assert_eq!(graph.nodes.len(), 2);
1043 assert!(!graph.relationships.is_empty());
1044 assert!(!graph.clusters.is_empty());
1045
1046 assert_eq!(graph.statistics.total_nodes, 2);
1048 assert!(graph.statistics.total_relationships > 0);
1049 assert!(graph.statistics.avg_relationships_per_node >= 0.0);
1050
1051 assert!(graph.metadata.contains_key("build_timestamp"));
1053 }
1054
1055 #[test]
1056 fn test_calculate_statistics() {
1057 let allocations = vec![
1058 create_test_allocation(
1059 0x1000,
1060 1024,
1061 Some("var1".to_string()),
1062 Some("i32".to_string()),
1063 Some("main".to_string()),
1064 ),
1065 create_test_allocation(
1066 0x2000,
1067 512,
1068 Some("var2".to_string()),
1069 Some("String".to_string()),
1070 Some("main".to_string()),
1071 ),
1072 create_test_allocation(
1073 0x3000,
1074 256,
1075 Some("isolated".to_string()),
1076 Some("f64".to_string()),
1077 Some("other".to_string()),
1078 ),
1079 ];
1080
1081 let registry = HashMap::new();
1082
1083 let graph = VariableRelationshipBuilder::new()
1084 .add_allocations(&allocations, ®istry)
1085 .detect_scope_relationships()
1086 .build_graph();
1087
1088 let stats = &graph.statistics;
1089 assert_eq!(stats.total_nodes, 3);
1090 assert!(stats.total_relationships > 0);
1091 assert!(stats.largest_cluster_size >= 2); assert!(stats.isolated_nodes <= 1); assert!(stats.avg_relationships_per_node >= 0.0);
1094 }
1095
1096 #[test]
1097 fn test_smart_pointer_info_conversion() {
1098 let allocations = vec![create_smart_pointer_allocation(
1099 0x1000,
1100 64,
1101 "rc_ptr".to_string(),
1102 "Rc<Data>".to_string(),
1103 SmartPointerType::Rc,
1104 0x2000,
1105 vec![0x3000],
1106 Some(0x4000),
1107 )];
1108
1109 let registry = HashMap::new();
1110
1111 let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
1112
1113 let node = &builder.nodes["0x1000"];
1114 assert!(node.smart_pointer_info.is_some());
1115
1116 let smart_ptr_info = node.smart_pointer_info.as_ref().unwrap();
1117 assert_eq!(smart_ptr_info.pointer_type, "Rc");
1118 assert_eq!(smart_ptr_info.ref_count, Some(1));
1119 assert_eq!(smart_ptr_info.data_ptr, Some(0x2000));
1120 assert_eq!(smart_ptr_info.clones, vec![0x3000]);
1121 assert_eq!(smart_ptr_info.cloned_from, Some(0x4000));
1122 }
1123
1124 #[test]
1125 fn test_layout_hint_serialization() {
1126 let layout_hint = LayoutHint {
1127 x: 10.0,
1128 y: 20.0,
1129 width: Some(100.0),
1130 height: Some(50.0),
1131 };
1132
1133 let serialized = serde_json::to_string(&layout_hint).expect("Failed to serialize");
1134 let deserialized: LayoutHint =
1135 serde_json::from_str(&serialized).expect("Failed to deserialize");
1136
1137 assert_eq!(deserialized.x, 10.0);
1138 assert_eq!(deserialized.y, 20.0);
1139 assert_eq!(deserialized.width, Some(100.0));
1140 assert_eq!(deserialized.height, Some(50.0));
1141 }
1142
1143 #[test]
1144 fn test_variable_node_serialization() {
1145 let node = VariableNode {
1146 id: "0x1000".to_string(),
1147 name: "test_var".to_string(),
1148 type_name: "i32".to_string(),
1149 size: 4,
1150 scope: "main".to_string(),
1151 is_active: true,
1152 category: VariableCategory::UserVariable,
1153 smart_pointer_info: None,
1154 created_at: 1000,
1155 destroyed_at: None,
1156 };
1157
1158 let serialized = serde_json::to_string(&node).expect("Failed to serialize");
1159 let deserialized: VariableNode =
1160 serde_json::from_str(&serialized).expect("Failed to deserialize");
1161
1162 assert_eq!(deserialized.id, "0x1000");
1163 assert_eq!(deserialized.name, "test_var");
1164 assert_eq!(deserialized.type_name, "i32");
1165 assert_eq!(deserialized.size, 4);
1166 assert!(deserialized.is_active);
1167 }
1168
1169 #[test]
1170 fn test_variable_relationship_serialization() {
1171 let mut metadata = HashMap::new();
1172 metadata.insert(
1173 "test_key".to_string(),
1174 serde_json::Value::String("test_value".to_string()),
1175 );
1176
1177 let relationship = VariableRelationship {
1178 source: "0x1000".to_string(),
1179 target: "0x2000".to_string(),
1180 relationship_type: RelationshipType::References,
1181 weight: 0.8,
1182 metadata,
1183 };
1184
1185 let serialized = serde_json::to_string(&relationship).expect("Failed to serialize");
1186 let deserialized: VariableRelationship =
1187 serde_json::from_str(&serialized).expect("Failed to deserialize");
1188
1189 assert_eq!(deserialized.source, "0x1000");
1190 assert_eq!(deserialized.target, "0x2000");
1191 assert_eq!(deserialized.relationship_type, RelationshipType::References);
1192 assert_eq!(deserialized.weight, 0.8);
1193 assert!(deserialized.metadata.contains_key("test_key"));
1194 }
1195
1196 #[test]
1197 fn test_build_variable_relationship_graph_function() {
1198 let allocations = vec![
1199 create_test_allocation(
1200 0x1000,
1201 1024,
1202 Some("var1".to_string()),
1203 Some("i32".to_string()),
1204 Some("main".to_string()),
1205 ),
1206 create_test_allocation(
1207 0x2000,
1208 512,
1209 Some("var2".to_string()),
1210 Some("String".to_string()),
1211 Some("main".to_string()),
1212 ),
1213 ];
1214
1215 let registry = HashMap::new();
1216
1217 let result = build_variable_relationship_graph(&allocations, ®istry);
1218 assert!(result.is_ok());
1219
1220 let graph = result.unwrap();
1221 assert_eq!(graph.nodes.len(), 2);
1222 assert!(!graph.relationships.is_empty());
1223 assert!(!graph.clusters.is_empty());
1224 }
1225
1226 #[test]
1227 fn test_comprehensive_workflow() {
1228 let allocations = vec![
1230 create_test_allocation(
1232 0x1000,
1233 1024,
1234 Some("main_var1".to_string()),
1235 Some("i32".to_string()),
1236 Some("main".to_string()),
1237 ),
1238 create_test_allocation(
1239 0x2000,
1240 512,
1241 Some("main_var2".to_string()),
1242 Some("String".to_string()),
1243 Some("main".to_string()),
1244 ),
1245 create_test_allocation(
1247 0x3000,
1248 256,
1249 Some("func_var".to_string()),
1250 Some("f64".to_string()),
1251 Some("function".to_string()),
1252 ),
1253 create_smart_pointer_allocation(
1255 0x4000,
1256 64,
1257 "rc1".to_string(),
1258 "Rc<Data>".to_string(),
1259 SmartPointerType::Rc,
1260 0x6000,
1261 vec![0x5000],
1262 None,
1263 ),
1264 create_smart_pointer_allocation(
1265 0x5000,
1266 64,
1267 "rc2".to_string(),
1268 "Rc<Data>".to_string(),
1269 SmartPointerType::Rc,
1270 0x6000,
1271 vec![],
1272 Some(0x4000),
1273 ),
1274 create_smart_pointer_allocation(
1276 0x7000,
1277 64,
1278 "box_ptr".to_string(),
1279 "Box<String>".to_string(),
1280 SmartPointerType::Box,
1281 0x8000,
1282 vec![],
1283 None,
1284 ),
1285 create_test_allocation(
1286 0x8000,
1287 32,
1288 Some("boxed_data".to_string()),
1289 Some("String".to_string()),
1290 Some("main".to_string()),
1291 ),
1292 create_test_allocation(
1294 0x9000,
1295 128,
1296 Some("vec_data".to_string()),
1297 Some("Vec<i32>".to_string()),
1298 Some("main".to_string()),
1299 ),
1300 ];
1301
1302 let mut registry = HashMap::new();
1303 registry.insert(
1304 0x1000,
1305 create_test_variable_info("registry_main_var".to_string(), "u32".to_string()),
1306 );
1307
1308 let graph = VariableRelationshipBuilder::new()
1309 .add_allocations(&allocations, ®istry)
1310 .detect_references()
1311 .detect_scope_relationships()
1312 .detect_circular_references()
1313 .build_graph();
1314
1315 assert_eq!(graph.nodes.len(), 8);
1317 assert!(!graph.relationships.is_empty());
1318 assert!(!graph.clusters.is_empty());
1319
1320 let has_clones = graph
1322 .relationships
1323 .iter()
1324 .any(|rel| rel.relationship_type == RelationshipType::Clones);
1325 let has_ownership = graph
1326 .relationships
1327 .iter()
1328 .any(|rel| rel.relationship_type == RelationshipType::Owns);
1329 let has_containment = graph
1330 .relationships
1331 .iter()
1332 .any(|rel| rel.relationship_type == RelationshipType::Contains);
1333
1334 assert!(has_clones || has_ownership || has_containment);
1335
1336 let mut categories = Vec::new();
1338
1339 for node in &graph.nodes {
1341 if !categories.contains(&&node.category) {
1342 categories.push(&node.category);
1343 }
1344 }
1345
1346 assert!(
1348 !categories.is_empty(),
1349 "Expected at least one variable category"
1350 );
1351
1352 let has_user_vars = categories.contains(&&VariableCategory::UserVariable);
1354 assert!(has_user_vars, "Expected at least one user variable");
1355
1356 let stats = &graph.statistics;
1358 assert_eq!(stats.total_nodes, 8);
1359 assert!(stats.total_relationships > 0);
1360 assert!(stats.avg_relationships_per_node >= 0.0);
1361
1362 assert!(graph.metadata.contains_key("build_timestamp"));
1364
1365 let scope_clusters: Vec<_> = graph
1367 .clusters
1368 .iter()
1369 .filter(|cluster| cluster.cluster_type == ClusterType::Scope)
1370 .collect();
1371
1372 assert!(!scope_clusters.is_empty());
1373
1374 let main_cluster = scope_clusters
1376 .iter()
1377 .find(|cluster| cluster.variables.len() > 1);
1378
1379 assert!(main_cluster.is_some());
1380 }
1381
1382 #[test]
1383 fn test_circular_reference_relationships_use_ptr_not_stack_address() {
1384 let allocations = vec![
1385 create_smart_pointer_allocation(
1386 0x1000,
1387 64,
1388 "rc1".to_string(),
1389 "Rc<Data>".to_string(),
1390 SmartPointerType::Rc,
1391 0x5000,
1392 vec![0x2000],
1393 None,
1394 ),
1395 create_smart_pointer_allocation(
1396 0x2000,
1397 64,
1398 "rc2".to_string(),
1399 "Rc<Data>".to_string(),
1400 SmartPointerType::Rc,
1401 0x5000,
1402 vec![],
1403 Some(0x1000),
1404 ),
1405 ];
1406
1407 let registry = HashMap::new();
1408
1409 let graph = VariableRelationshipBuilder::new()
1410 .add_allocations(&allocations, ®istry)
1411 .detect_references()
1412 .detect_circular_references()
1413 .build_graph();
1414
1415 let circular_rels: Vec<_> = graph
1416 .relationships
1417 .iter()
1418 .filter(|rel| {
1419 rel.metadata
1420 .get("circular_reference")
1421 .and_then(|v| v.as_bool())
1422 .unwrap_or(false)
1423 })
1424 .collect();
1425
1426 for rel in &circular_rels {
1427 assert!(
1428 rel.source.starts_with("0x"),
1429 "Circular reference source should use pointer format '0x...', got: {}",
1430 rel.source
1431 );
1432 assert!(
1433 rel.target.starts_with("0x"),
1434 "Circular reference target should use pointer format '0x...', got: {}",
1435 rel.target
1436 );
1437 let source_addr: usize =
1438 usize::from_str_radix(rel.source.trim_start_matches("0x"), 16).unwrap();
1439 let target_addr: usize =
1440 usize::from_str_radix(rel.target.trim_start_matches("0x"), 16).unwrap();
1441 assert!(
1442 source_addr == 0x1000 || source_addr == 0x2000,
1443 "Circular reference source should be 0x1000 or 0x2000, got: {:#x}",
1444 source_addr
1445 );
1446 assert!(
1447 target_addr == 0x1000 || target_addr == 0x2000,
1448 "Circular reference target should be 0x1000 or 0x2000, got: {:#x}",
1449 target_addr
1450 );
1451 }
1452 }
1453}