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