Skip to main content

memscope_rs/analysis/
variable_relationships.rs

1//! Variable Relationship Analysis
2//!
3//! This module provides functionality to detect and analyze relationships between variables,
4//! building a comprehensive graph for visualization and analysis.
5
6use crate::capture::types::{AllocationInfo, TrackingResult};
7use crate::variable_registry::VariableInfo;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, HashSet};
10
11/// Types of relationships between variables
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
13pub enum RelationshipType {
14    /// One variable references another (through pointers)
15    References,
16    /// One variable owns another's memory (Box, unique ownership)
17    Owns,
18    /// Variables share the same data (Rc/Arc clones)
19    Clones,
20    /// Variables are in the same scope (containment relationship)
21    Contains,
22    /// One variable depends on another for its existence
23    DependsOn,
24}
25
26/// A node in the variable relationship graph
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct VariableNode {
29    /// Unique identifier (usually memory address as string)
30    pub id: String,
31    /// Variable name (user-defined or inferred)
32    pub name: String,
33    /// Type name
34    pub type_name: String,
35    /// Memory size in bytes
36    pub size: usize,
37    /// Scope name
38    pub scope: String,
39    /// Whether the variable is still active
40    pub is_active: bool,
41    /// Category of the variable
42    pub category: VariableCategory,
43    /// Smart pointer information if applicable
44    pub smart_pointer_info: Option<SmartPointerInfo>,
45    /// Timestamp when variable was created
46    pub created_at: u64,
47    /// Timestamp when variable was destroyed (if applicable)
48    pub destroyed_at: Option<u64>,
49}
50
51/// Category of variable for visualization purposes
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53pub enum VariableCategory {
54    /// User-defined variable tracked explicitly
55    UserVariable,
56    /// System allocation inferred automatically
57    SystemAllocation,
58    /// Smart pointer (Rc, Arc, Box)
59    SmartPointer,
60    /// Collection (Vec, HashMap, etc.)
61    Collection,
62}
63
64/// Smart pointer specific information
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct SmartPointerInfo {
67    /// Type of smart pointer (Rc, Arc, Box)
68    pub pointer_type: String,
69    /// Reference count (for Rc/Arc)
70    pub ref_count: Option<usize>,
71    /// Address of the data being pointed to
72    pub data_ptr: Option<usize>,
73    /// List of clone addresses (for Rc/Arc)
74    pub clones: Vec<usize>,
75    /// Address this was cloned from
76    pub cloned_from: Option<usize>,
77}
78
79/// A relationship edge between two variables
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct VariableRelationship {
82    /// Source variable ID
83    pub source: String,
84    /// Target variable ID
85    pub target: String,
86    /// Type of relationship
87    pub relationship_type: RelationshipType,
88    /// Strength/weight of the relationship (0.0 to 1.0)
89    pub weight: f64,
90    /// Additional metadata about the relationship
91    pub metadata: HashMap<String, serde_json::Value>,
92}
93
94/// A cluster of related variables (e.g., same scope, same type)
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct VariableCluster {
97    /// Unique cluster ID
98    pub id: String,
99    /// Type of cluster (scope, type, etc.)
100    pub cluster_type: ClusterType,
101    /// Variables in this cluster
102    pub variables: Vec<String>,
103    /// Suggested layout position for visualization
104    pub layout_hint: Option<LayoutHint>,
105    /// Cluster metadata
106    pub metadata: HashMap<String, serde_json::Value>,
107}
108
109/// Types of variable clusters
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111pub enum ClusterType {
112    /// Variables in the same scope
113    Scope,
114    /// Variables of the same type
115    Type,
116    /// Variables with similar lifetimes
117    Lifetime,
118    /// Variables in a smart pointer relationship
119    SmartPointerGroup,
120}
121
122/// Layout hint for visualization
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct LayoutHint {
125    /// Suggested X coordinate
126    pub x: f64,
127    /// Suggested Y coordinate
128    pub y: f64,
129    /// Suggested width
130    pub width: Option<f64>,
131    /// Suggested height
132    pub height: Option<f64>,
133}
134
135/// Complete variable relationship graph
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct VariableRelationshipGraph {
138    /// All nodes in the graph
139    pub nodes: Vec<VariableNode>,
140    /// All relationships between nodes
141    pub relationships: Vec<VariableRelationship>,
142    /// Clusters of related variables
143    pub clusters: Vec<VariableCluster>,
144    /// Graph statistics
145    pub statistics: GraphStatistics,
146    /// Metadata about the graph
147    pub metadata: HashMap<String, serde_json::Value>,
148}
149
150/// Statistics about the relationship graph
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct GraphStatistics {
153    /// Total number of nodes
154    pub total_nodes: usize,
155    /// Total number of relationships
156    pub total_relationships: usize,
157    /// Number of circular references detected
158    pub circular_references: usize,
159    /// Size of the largest cluster
160    pub largest_cluster_size: usize,
161    /// Number of isolated nodes (no relationships)
162    pub isolated_nodes: usize,
163    /// Average relationships per node
164    pub avg_relationships_per_node: f64,
165}
166
167/// Builder for constructing variable relationship graphs
168pub struct VariableRelationshipBuilder {
169    nodes: HashMap<String, VariableNode>,
170    relationships: Vec<VariableRelationship>,
171    clusters: Vec<VariableCluster>,
172}
173
174impl VariableRelationshipBuilder {
175    /// Create a new relationship builder
176    pub fn new() -> Self {
177        Self {
178            nodes: HashMap::new(),
179            relationships: Vec::new(),
180            clusters: Vec::new(),
181        }
182    }
183
184    /// Add allocations to the graph
185    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    /// Detect reference relationships between variables
198    pub fn detect_references(mut self) -> Self {
199        // Look for smart pointer relationships
200        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                // Create relationships for smart pointer clones
205                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                // Create relationship to cloned_from
219                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                // Create ownership relationship for Box pointers
233                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    /// Detect scope-based relationships
254    pub fn detect_scope_relationships(mut self) -> Self {
255        // Group variables by scope
256        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        // Create scope clusters and containment relationships
266        for (scope_name, variable_ids) in scope_groups {
267            if variable_ids.len() > 1 {
268                // Create cluster
269                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                // Create containment relationships within scope
285                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, // Lower weight for scope relationships
292                            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    /// Detect circular references
310    pub fn detect_circular_references(mut self) -> Self {
311        // Use existing circular reference detection logic
312        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        // Add circular reference relationships
322        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, // High weight for circular dependencies
334                            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    /// Build the final relationship graph
359    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    /// Create a node from allocation info
384    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        // Get variable info from registry or infer
392        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    /// Convert node back to allocation info for analysis
461    fn node_to_allocation_info(&self, node: &VariableNode) -> Option<AllocationInfo> {
462        let ptr = usize::from_str_radix(&node.id[2..], 16).ok()?;
463
464        // Create AllocationInfo with basic information
465        let mut alloc = AllocationInfo::new(ptr, node.size);
466
467        // Preserve smart pointer information if available
468        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    /// Calculate graph statistics
503    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        // Find isolated nodes (nodes with no relationships)
526        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
556/// Build a complete variable relationship graph from allocations and registry
557pub 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    /// Helper function to create test allocation info
584    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    /// Helper function to create test allocation with smart pointer info
633    #[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    /// Helper function to create test variable info
672    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, &registry);
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, &registry);
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, &registry);
821
822        assert_eq!(builder.nodes.len(), 4);
823
824        // Check that nodes are created with appropriate categories
825        // The exact categorization may depend on the inference logic implementation
826        let categories: Vec<_> = builder.nodes.values().map(|node| &node.category).collect();
827
828        // We should have some user variables since we provided explicit type names
829        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        // At least one of these should be true
843        assert!(has_user_vars || has_smart_ptrs || has_collections || has_system_allocs);
844
845        // Verify specific nodes exist
846        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, &registry)
891            .detect_references();
892
893        // Should have clone relationships
894        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        // Check that relationships exist between clones
905        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, &registry)
938            .detect_references();
939
940        // Should have ownership relationship
941        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, &registry)
986            .detect_scope_relationships();
987
988        // Should have scope clusters
989        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        // Should have main scope cluster with 2 variables
1000        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        // Should have containment relationships within scope
1011        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, &registry)
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        // Check statistics
1051        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        // Check metadata
1056        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, &registry)
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); // main scope cluster
1096        assert!(stats.isolated_nodes <= 1); // isolated variable might not have relationships
1097        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, &registry);
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, &registry);
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        // Create a complex scenario with multiple types of relationships
1233        let allocations = vec![
1234            // Main scope variables
1235            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            // Function scope variable
1250            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            // Smart pointers
1258            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            // Box pointer
1279            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            // Collections
1297            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, &registry)
1314            .detect_references()
1315            .detect_scope_relationships()
1316            .detect_circular_references()
1317            .build_graph();
1318
1319        // Verify comprehensive results
1320        assert_eq!(graph.nodes.len(), 8);
1321        assert!(!graph.relationships.is_empty());
1322        assert!(!graph.clusters.is_empty());
1323
1324        // Check for different types of relationships
1325        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        // Check for different categories - be flexible about categorization
1341        let mut categories = Vec::new();
1342
1343        // Collect all unique categories
1344        for node in &graph.nodes {
1345            if !categories.contains(&&node.category) {
1346                categories.push(&node.category);
1347            }
1348        }
1349
1350        // We should have at least one category
1351        assert!(
1352            !categories.is_empty(),
1353            "Expected at least one variable category"
1354        );
1355
1356        // We should have user variables since we provided explicit names and types
1357        let has_user_vars = categories.contains(&&VariableCategory::UserVariable);
1358        assert!(has_user_vars, "Expected at least one user variable");
1359
1360        // Check statistics
1361        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        // Check metadata
1367        assert!(graph.metadata.contains_key("build_timestamp"));
1368
1369        // Verify clusters exist
1370        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        // Should have main scope cluster with multiple variables
1379        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, &registry)
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}