use crate::capture::types::{AllocationInfo, TrackingResult};
use crate::variable_registry::VariableInfo;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum RelationshipType {
References,
Owns,
Clones,
Contains,
DependsOn,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableNode {
pub id: String,
pub name: String,
pub type_name: String,
pub size: usize,
pub scope: String,
pub is_active: bool,
pub category: VariableCategory,
pub smart_pointer_info: Option<SmartPointerInfo>,
pub created_at: u64,
pub destroyed_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum VariableCategory {
UserVariable,
SystemAllocation,
SmartPointer,
Collection,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SmartPointerInfo {
pub pointer_type: String,
pub ref_count: Option<usize>,
pub data_ptr: Option<usize>,
pub clones: Vec<usize>,
pub cloned_from: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableRelationship {
pub source: String,
pub target: String,
pub relationship_type: RelationshipType,
pub weight: f64,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableCluster {
pub id: String,
pub cluster_type: ClusterType,
pub variables: Vec<String>,
pub layout_hint: Option<LayoutHint>,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ClusterType {
Scope,
Type,
Lifetime,
SmartPointerGroup,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayoutHint {
pub x: f64,
pub y: f64,
pub width: Option<f64>,
pub height: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableRelationshipGraph {
pub nodes: Vec<VariableNode>,
pub relationships: Vec<VariableRelationship>,
pub clusters: Vec<VariableCluster>,
pub statistics: GraphStatistics,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphStatistics {
pub total_nodes: usize,
pub total_relationships: usize,
pub circular_references: usize,
pub largest_cluster_size: usize,
pub isolated_nodes: usize,
pub avg_relationships_per_node: f64,
}
pub struct VariableRelationshipBuilder {
nodes: HashMap<String, VariableNode>,
relationships: Vec<VariableRelationship>,
clusters: Vec<VariableCluster>,
}
impl VariableRelationshipBuilder {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
relationships: Vec::new(),
clusters: Vec::new(),
}
}
pub fn add_allocations(
mut self,
allocations: &[AllocationInfo],
registry: &HashMap<usize, VariableInfo>,
) -> Self {
for alloc in allocations {
let node = self.create_node_from_allocation(alloc, registry);
self.nodes.insert(node.id.clone(), node);
}
self
}
pub fn detect_references(mut self) -> Self {
let nodes: Vec<_> = self.nodes.values().cloned().collect();
for node in &nodes {
if let Some(smart_ptr_info) = &node.smart_pointer_info {
for &clone_addr in &smart_ptr_info.clones {
let clone_id = format!("0x{clone_addr:x}");
if self.nodes.contains_key(&clone_id) {
self.relationships.push(VariableRelationship {
source: node.id.clone(),
target: clone_id,
relationship_type: RelationshipType::Clones,
weight: 1.0,
metadata: HashMap::new(),
});
}
}
if let Some(cloned_from_addr) = smart_ptr_info.cloned_from {
let parent_id = format!("0x{cloned_from_addr:x}");
if self.nodes.contains_key(&parent_id) {
self.relationships.push(VariableRelationship {
source: parent_id,
target: node.id.clone(),
relationship_type: RelationshipType::Clones,
weight: 1.0,
metadata: HashMap::new(),
});
}
}
if smart_ptr_info.pointer_type == "Box" {
if let Some(data_ptr) = smart_ptr_info.data_ptr {
let data_id = format!("0x{data_ptr:x}");
if self.nodes.contains_key(&data_id) {
self.relationships.push(VariableRelationship {
source: node.id.clone(),
target: data_id,
relationship_type: RelationshipType::Owns,
weight: 1.0,
metadata: HashMap::new(),
});
}
}
}
}
}
self
}
pub fn detect_scope_relationships(mut self) -> Self {
let mut scope_groups: HashMap<String, Vec<String>> = HashMap::new();
for node in self.nodes.values() {
scope_groups
.entry(node.scope.clone())
.or_default()
.push(node.id.clone());
}
for (scope_name, variable_ids) in scope_groups {
if variable_ids.len() > 1 {
self.clusters.push(VariableCluster {
id: format!("scope_{scope_name}"),
cluster_type: ClusterType::Scope,
variables: variable_ids.clone(),
layout_hint: None,
metadata: {
let mut meta = HashMap::new();
meta.insert(
"scope_name".to_string(),
serde_json::Value::String(scope_name.clone()),
);
meta
},
});
for i in 0..variable_ids.len() {
for j in (i + 1)..variable_ids.len() {
self.relationships.push(VariableRelationship {
source: variable_ids[i].clone(),
target: variable_ids[j].clone(),
relationship_type: RelationshipType::Contains,
weight: 0.3, metadata: {
let mut meta = HashMap::new();
meta.insert(
"scope".to_string(),
serde_json::Value::String(scope_name.clone()),
);
meta
},
});
}
}
}
}
self
}
pub fn detect_circular_references(mut self) -> Self {
let allocations: Vec<AllocationInfo> = self
.nodes
.values()
.filter_map(|node| self.node_to_allocation_info(node))
.collect();
let circular_analysis =
crate::analysis::circular_reference::detect_circular_references(&allocations);
for cycle in &circular_analysis.circular_references {
for window in cycle.cycle_path.windows(2) {
if let (Some(source), Some(target)) = (window.first(), window.last()) {
let source_id = format!("0x{:x}", source.ptr);
let target_id = format!("0x{:x}", target.ptr);
if self.nodes.contains_key(&source_id) && self.nodes.contains_key(&target_id) {
self.relationships.push(VariableRelationship {
source: source_id,
target: target_id,
relationship_type: RelationshipType::DependsOn,
weight: 0.8, metadata: {
let mut meta = HashMap::new();
meta.insert(
"circular_reference".to_string(),
serde_json::Value::Bool(true),
);
meta.insert(
"cycle_id".to_string(),
serde_json::Value::String(format!(
"cycle_{}",
cycle.cycle_path.len()
)),
);
meta
},
});
}
}
}
}
self
}
pub fn build_graph(self) -> VariableRelationshipGraph {
let statistics = self.calculate_statistics();
VariableRelationshipGraph {
nodes: self.nodes.into_values().collect(),
relationships: self.relationships,
clusters: self.clusters,
statistics,
metadata: {
let mut meta = HashMap::new();
meta.insert(
"build_timestamp".to_string(),
serde_json::Value::Number(serde_json::Number::from(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
)),
);
meta
},
}
}
fn create_node_from_allocation(
&self,
alloc: &AllocationInfo,
registry: &HashMap<usize, VariableInfo>,
) -> VariableNode {
let id = format!("0x{:x}", alloc.ptr);
let (name, type_name, category) = if let Some(var_info) = registry.get(&alloc.ptr) {
(
var_info.var_name.clone(),
var_info.type_name.clone(),
VariableCategory::UserVariable,
)
} else if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
(
var_name.clone(),
type_name.clone(),
VariableCategory::UserVariable,
)
} else {
let (inferred_name, inferred_type) =
crate::variable_registry::VariableRegistry::infer_allocation_info_cached(alloc);
let category = if inferred_type.contains("Vec") || inferred_type.contains("HashMap") {
VariableCategory::Collection
} else if inferred_type.contains("Box")
|| inferred_type.contains("Rc")
|| inferred_type.contains("Arc")
{
VariableCategory::SmartPointer
} else {
VariableCategory::SystemAllocation
};
(inferred_name, inferred_type, category)
};
let scope = alloc
.scope_name
.as_deref()
.unwrap_or_else(|| {
if category == VariableCategory::UserVariable {
"main"
} else {
"system"
}
})
.to_string();
VariableNode {
id,
name,
type_name,
size: alloc.size,
scope,
is_active: alloc.timestamp_dealloc.is_none(),
category,
smart_pointer_info: alloc
.smart_pointer_info
.as_ref()
.map(|info| SmartPointerInfo {
pointer_type: format!("{:?}", info.pointer_type),
ref_count: Some(
info.ref_count_history
.last()
.map(|s| s.strong_count)
.unwrap_or(0),
),
data_ptr: Some(info.data_ptr),
clones: info.clones.clone(),
cloned_from: info.cloned_from,
}),
created_at: alloc.timestamp_alloc,
destroyed_at: alloc.timestamp_dealloc,
}
}
fn node_to_allocation_info(&self, node: &VariableNode) -> Option<AllocationInfo> {
let ptr = usize::from_str_radix(&node.id[2..], 16).ok()?;
let mut alloc = AllocationInfo::new(ptr, node.size);
if let Some(node_sp_info) = &node.smart_pointer_info {
let sp_info = crate::capture::types::smart_pointer::SmartPointerInfo {
data_ptr: node_sp_info.data_ptr.unwrap_or(ptr),
cloned_from: node_sp_info.cloned_from,
clones: node_sp_info.clones.clone(),
ref_count_history: if let Some(ref_count) = node_sp_info.ref_count {
vec![crate::capture::types::smart_pointer::RefCountSnapshot {
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64,
strong_count: ref_count,
weak_count: 0,
}]
} else {
Vec::new()
},
weak_count: None,
is_weak_reference: false,
is_data_owner: node_sp_info.ref_count.is_none_or(|c| c > 0),
is_implicitly_deallocated: false,
pointer_type: match node_sp_info.pointer_type.as_str() {
"Rc" => crate::capture::types::smart_pointer::SmartPointerType::Rc,
"Arc" => crate::capture::types::smart_pointer::SmartPointerType::Arc,
"Box" => crate::capture::types::smart_pointer::SmartPointerType::Box,
_ => crate::capture::types::smart_pointer::SmartPointerType::Rc,
},
};
alloc.set_smart_pointer_info(sp_info);
}
Some(alloc)
}
fn calculate_statistics(&self) -> GraphStatistics {
let total_nodes = self.nodes.len();
let total_relationships = self.relationships.len();
let circular_references = self
.relationships
.iter()
.filter(|rel| {
rel.metadata
.get("circular_reference")
.and_then(|v| v.as_bool())
.unwrap_or(false)
})
.count();
let largest_cluster_size = self
.clusters
.iter()
.map(|cluster| cluster.variables.len())
.max()
.unwrap_or(0);
let mut connected_nodes = HashSet::new();
for rel in &self.relationships {
connected_nodes.insert(&rel.source);
connected_nodes.insert(&rel.target);
}
let isolated_nodes = total_nodes - connected_nodes.len();
let avg_relationships_per_node = if total_nodes > 0 {
total_relationships as f64 / total_nodes as f64
} else {
0.0
};
GraphStatistics {
total_nodes,
total_relationships,
circular_references,
largest_cluster_size,
isolated_nodes,
avg_relationships_per_node,
}
}
}
impl Default for VariableRelationshipBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn build_variable_relationship_graph(
allocations: &[AllocationInfo],
registry: &HashMap<usize, VariableInfo>,
) -> TrackingResult<VariableRelationshipGraph> {
let graph = VariableRelationshipBuilder::new()
.add_allocations(allocations, registry)
.detect_references()
.detect_scope_relationships()
.detect_circular_references()
.build_graph();
Ok(graph)
}
#[cfg(test)]
mod tests {
use std::thread;
use super::*;
use crate::capture::types::{
AllocationInfo, RefCountSnapshot, SmartPointerInfo as CoreSmartPointerInfo,
SmartPointerType,
};
use crate::utils::current_thread_id_u64;
use crate::variable_registry::VariableInfo;
fn create_test_allocation(
ptr: usize,
size: usize,
var_name: Option<String>,
type_name: Option<String>,
scope_name: Option<String>,
) -> AllocationInfo {
let thread_id = thread::current().id();
let thread_id_u64 = current_thread_id_u64();
AllocationInfo {
ptr,
size,
var_name,
type_name,
scope_name,
timestamp_alloc: 1000,
timestamp_dealloc: None,
thread_id,
thread_id_u64,
borrow_count: 0,
stack_trace: Some(vec!["test_function".to_string()]),
is_leaked: false,
lifetime_ms: None,
borrow_info: None,
clone_info: None,
ownership_history_available: false,
smart_pointer_info: None,
memory_layout: None,
generic_info: None,
dynamic_type_info: None,
runtime_state: None,
stack_allocation: None,
temporary_object: None,
fragmentation_analysis: None,
generic_instantiation: None,
type_relationships: None,
type_usage: None,
function_call_tracking: None,
lifecycle_tracking: None,
access_tracking: None,
drop_chain_analysis: None,
}
}
#[allow(clippy::too_many_arguments)]
fn create_smart_pointer_allocation(
ptr: usize,
size: usize,
var_name: String,
type_name: String,
pointer_type: SmartPointerType,
data_ptr: usize,
clones: Vec<usize>,
cloned_from: Option<usize>,
) -> AllocationInfo {
let mut alloc = create_test_allocation(
ptr,
size,
Some(var_name),
Some(type_name),
Some("main".to_string()),
);
alloc.smart_pointer_info = Some(CoreSmartPointerInfo {
data_ptr,
pointer_type,
ref_count_history: vec![RefCountSnapshot {
timestamp: 1000,
strong_count: 1,
weak_count: 0,
}],
weak_count: Some(0),
is_data_owner: true,
is_weak_reference: false,
is_implicitly_deallocated: false,
clones,
cloned_from,
});
alloc
}
fn create_test_variable_info(var_name: String, type_name: String) -> VariableInfo {
VariableInfo {
var_name,
type_name,
timestamp: 1000,
size: 64,
thread_id: 1,
memory_usage: 64,
}
}
#[test]
fn test_relationship_type_serialization() {
let relationship_types = vec![
RelationshipType::References,
RelationshipType::Owns,
RelationshipType::Clones,
RelationshipType::Contains,
RelationshipType::DependsOn,
];
for rel_type in relationship_types {
let serialized = serde_json::to_string(&rel_type).expect("Failed to serialize");
let _deserialized: RelationshipType =
serde_json::from_str(&serialized).expect("Failed to deserialize");
}
}
#[test]
fn test_variable_category_serialization() {
let categories = vec![
VariableCategory::UserVariable,
VariableCategory::SystemAllocation,
VariableCategory::SmartPointer,
VariableCategory::Collection,
];
for category in categories {
let serialized = serde_json::to_string(&category).expect("Failed to serialize");
let _deserialized: VariableCategory =
serde_json::from_str(&serialized).expect("Failed to deserialize");
}
}
#[test]
fn test_cluster_type_serialization() {
let cluster_types = vec![
ClusterType::Scope,
ClusterType::Type,
ClusterType::Lifetime,
ClusterType::SmartPointerGroup,
];
for cluster_type in cluster_types {
let serialized = serde_json::to_string(&cluster_type).expect("Failed to serialize");
let _deserialized: ClusterType =
serde_json::from_str(&serialized).expect("Failed to deserialize");
}
}
#[test]
fn test_variable_relationship_builder_creation() {
let builder = VariableRelationshipBuilder::new();
assert!(builder.nodes.is_empty());
assert!(builder.relationships.is_empty());
assert!(builder.clusters.is_empty());
}
#[test]
fn test_variable_relationship_builder_default() {
let builder = VariableRelationshipBuilder::default();
assert!(builder.nodes.is_empty());
assert!(builder.relationships.is_empty());
assert!(builder.clusters.is_empty());
}
#[test]
fn test_add_allocations_basic() {
let allocations = vec![
create_test_allocation(
0x1000,
1024,
Some("var1".to_string()),
Some("i32".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x2000,
512,
Some("var2".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
];
let registry = HashMap::new();
let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
assert_eq!(builder.nodes.len(), 2);
assert!(builder.nodes.contains_key("0x1000"));
assert!(builder.nodes.contains_key("0x2000"));
let node1 = &builder.nodes["0x1000"];
assert_eq!(node1.name, "var1");
assert_eq!(node1.type_name, "i32");
assert_eq!(node1.size, 1024);
assert_eq!(node1.scope, "main");
assert!(node1.is_active);
assert_eq!(node1.category, VariableCategory::UserVariable);
}
#[test]
fn test_add_allocations_with_registry() {
let allocations = vec![create_test_allocation(0x1000, 1024, None, None, None)];
let mut registry = HashMap::new();
registry.insert(
0x1000,
create_test_variable_info("registry_var".to_string(), "u64".to_string()),
);
let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
assert_eq!(builder.nodes.len(), 1);
let node = &builder.nodes["0x1000"];
assert_eq!(node.name, "registry_var");
assert_eq!(node.type_name, "u64");
assert_eq!(node.category, VariableCategory::UserVariable);
}
#[test]
fn test_add_allocations_inferred_categories() {
let allocations = vec![
create_test_allocation(0x1000, 1024, None, Some("Vec<i32>".to_string()), None),
create_test_allocation(0x2000, 512, None, Some("Box<String>".to_string()), None),
create_test_allocation(
0x3000,
256,
None,
Some("HashMap<String, i32>".to_string()),
None,
),
create_test_allocation(0x4000, 128, None, Some("Rc<Data>".to_string()), None),
];
let registry = HashMap::new();
let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
assert_eq!(builder.nodes.len(), 4);
let categories: Vec<_> = builder.nodes.values().map(|node| &node.category).collect();
let has_user_vars = categories
.iter()
.any(|&cat| *cat == VariableCategory::UserVariable);
let has_smart_ptrs = categories
.iter()
.any(|&cat| *cat == VariableCategory::SmartPointer);
let has_collections = categories
.iter()
.any(|&cat| *cat == VariableCategory::Collection);
let has_system_allocs = categories
.iter()
.any(|&cat| *cat == VariableCategory::SystemAllocation);
assert!(has_user_vars || has_smart_ptrs || has_collections || has_system_allocs);
assert!(builder.nodes.contains_key("0x1000"));
assert!(builder.nodes.contains_key("0x2000"));
assert!(builder.nodes.contains_key("0x3000"));
assert!(builder.nodes.contains_key("0x4000"));
}
#[test]
fn test_detect_references_smart_pointers() {
let allocations = vec![
create_smart_pointer_allocation(
0x1000,
64,
"rc1".to_string(),
"Rc<i32>".to_string(),
SmartPointerType::Rc,
0x5000,
vec![0x2000, 0x3000],
None,
),
create_smart_pointer_allocation(
0x2000,
64,
"rc2".to_string(),
"Rc<i32>".to_string(),
SmartPointerType::Rc,
0x5000,
vec![],
Some(0x1000),
),
create_smart_pointer_allocation(
0x3000,
64,
"rc3".to_string(),
"Rc<i32>".to_string(),
SmartPointerType::Rc,
0x5000,
vec![],
Some(0x1000),
),
];
let registry = HashMap::new();
let builder = VariableRelationshipBuilder::new()
.add_allocations(&allocations, ®istry)
.detect_references();
assert!(!builder.relationships.is_empty());
let clone_relationships: Vec<_> = builder
.relationships
.iter()
.filter(|rel| rel.relationship_type == RelationshipType::Clones)
.collect();
assert!(!clone_relationships.is_empty());
let has_clone_rel = clone_relationships.iter().any(|rel| {
rel.source == "0x1000" && (rel.target == "0x2000" || rel.target == "0x3000")
});
assert!(has_clone_rel);
}
#[test]
fn test_detect_references_box_ownership() {
let allocations = vec![
create_smart_pointer_allocation(
0x1000,
64,
"box_ptr".to_string(),
"Box<String>".to_string(),
SmartPointerType::Box,
0x2000,
vec![],
None,
),
create_test_allocation(
0x2000,
32,
Some("data".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
];
let registry = HashMap::new();
let builder = VariableRelationshipBuilder::new()
.add_allocations(&allocations, ®istry)
.detect_references();
let ownership_relationships: Vec<_> = builder
.relationships
.iter()
.filter(|rel| rel.relationship_type == RelationshipType::Owns)
.collect();
assert!(!ownership_relationships.is_empty());
let has_ownership = ownership_relationships
.iter()
.any(|rel| rel.source == "0x1000" && rel.target == "0x2000");
assert!(has_ownership);
}
#[test]
fn test_detect_scope_relationships() {
let allocations = vec![
create_test_allocation(
0x1000,
1024,
Some("var1".to_string()),
Some("i32".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x2000,
512,
Some("var2".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x3000,
256,
Some("var3".to_string()),
Some("f64".to_string()),
Some("function".to_string()),
),
];
let registry = HashMap::new();
let builder = VariableRelationshipBuilder::new()
.add_allocations(&allocations, ®istry)
.detect_scope_relationships();
assert!(!builder.clusters.is_empty());
let scope_clusters: Vec<_> = builder
.clusters
.iter()
.filter(|cluster| cluster.cluster_type == ClusterType::Scope)
.collect();
assert!(!scope_clusters.is_empty());
let main_cluster = scope_clusters
.iter()
.find(|cluster| cluster.id == "scope_main");
assert!(main_cluster.is_some());
let main_cluster = main_cluster.unwrap();
assert_eq!(main_cluster.variables.len(), 2);
assert!(main_cluster.variables.contains(&"0x1000".to_string()));
assert!(main_cluster.variables.contains(&"0x2000".to_string()));
let containment_relationships: Vec<_> = builder
.relationships
.iter()
.filter(|rel| rel.relationship_type == RelationshipType::Contains)
.collect();
assert!(!containment_relationships.is_empty());
}
#[test]
fn test_build_graph_basic() {
let allocations = vec![
create_test_allocation(
0x1000,
1024,
Some("var1".to_string()),
Some("i32".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x2000,
512,
Some("var2".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
];
let registry = HashMap::new();
let graph = VariableRelationshipBuilder::new()
.add_allocations(&allocations, ®istry)
.detect_scope_relationships()
.build_graph();
assert_eq!(graph.nodes.len(), 2);
assert!(!graph.relationships.is_empty());
assert!(!graph.clusters.is_empty());
assert_eq!(graph.statistics.total_nodes, 2);
assert!(graph.statistics.total_relationships > 0);
assert!(graph.statistics.avg_relationships_per_node >= 0.0);
assert!(graph.metadata.contains_key("build_timestamp"));
}
#[test]
fn test_calculate_statistics() {
let allocations = vec![
create_test_allocation(
0x1000,
1024,
Some("var1".to_string()),
Some("i32".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x2000,
512,
Some("var2".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x3000,
256,
Some("isolated".to_string()),
Some("f64".to_string()),
Some("other".to_string()),
),
];
let registry = HashMap::new();
let graph = VariableRelationshipBuilder::new()
.add_allocations(&allocations, ®istry)
.detect_scope_relationships()
.build_graph();
let stats = &graph.statistics;
assert_eq!(stats.total_nodes, 3);
assert!(stats.total_relationships > 0);
assert!(stats.largest_cluster_size >= 2); assert!(stats.isolated_nodes <= 1); assert!(stats.avg_relationships_per_node >= 0.0);
}
#[test]
fn test_smart_pointer_info_conversion() {
let allocations = vec![create_smart_pointer_allocation(
0x1000,
64,
"rc_ptr".to_string(),
"Rc<Data>".to_string(),
SmartPointerType::Rc,
0x2000,
vec![0x3000],
Some(0x4000),
)];
let registry = HashMap::new();
let builder = VariableRelationshipBuilder::new().add_allocations(&allocations, ®istry);
let node = &builder.nodes["0x1000"];
assert!(node.smart_pointer_info.is_some());
let smart_ptr_info = node.smart_pointer_info.as_ref().unwrap();
assert_eq!(smart_ptr_info.pointer_type, "Rc");
assert_eq!(smart_ptr_info.ref_count, Some(1));
assert_eq!(smart_ptr_info.data_ptr, Some(0x2000));
assert_eq!(smart_ptr_info.clones, vec![0x3000]);
assert_eq!(smart_ptr_info.cloned_from, Some(0x4000));
}
#[test]
fn test_layout_hint_serialization() {
let layout_hint = LayoutHint {
x: 10.0,
y: 20.0,
width: Some(100.0),
height: Some(50.0),
};
let serialized = serde_json::to_string(&layout_hint).expect("Failed to serialize");
let deserialized: LayoutHint =
serde_json::from_str(&serialized).expect("Failed to deserialize");
assert_eq!(deserialized.x, 10.0);
assert_eq!(deserialized.y, 20.0);
assert_eq!(deserialized.width, Some(100.0));
assert_eq!(deserialized.height, Some(50.0));
}
#[test]
fn test_variable_node_serialization() {
let node = VariableNode {
id: "0x1000".to_string(),
name: "test_var".to_string(),
type_name: "i32".to_string(),
size: 4,
scope: "main".to_string(),
is_active: true,
category: VariableCategory::UserVariable,
smart_pointer_info: None,
created_at: 1000,
destroyed_at: None,
};
let serialized = serde_json::to_string(&node).expect("Failed to serialize");
let deserialized: VariableNode =
serde_json::from_str(&serialized).expect("Failed to deserialize");
assert_eq!(deserialized.id, "0x1000");
assert_eq!(deserialized.name, "test_var");
assert_eq!(deserialized.type_name, "i32");
assert_eq!(deserialized.size, 4);
assert!(deserialized.is_active);
}
#[test]
fn test_variable_relationship_serialization() {
let mut metadata = HashMap::new();
metadata.insert(
"test_key".to_string(),
serde_json::Value::String("test_value".to_string()),
);
let relationship = VariableRelationship {
source: "0x1000".to_string(),
target: "0x2000".to_string(),
relationship_type: RelationshipType::References,
weight: 0.8,
metadata,
};
let serialized = serde_json::to_string(&relationship).expect("Failed to serialize");
let deserialized: VariableRelationship =
serde_json::from_str(&serialized).expect("Failed to deserialize");
assert_eq!(deserialized.source, "0x1000");
assert_eq!(deserialized.target, "0x2000");
assert_eq!(deserialized.relationship_type, RelationshipType::References);
assert_eq!(deserialized.weight, 0.8);
assert!(deserialized.metadata.contains_key("test_key"));
}
#[test]
fn test_build_variable_relationship_graph_function() {
let allocations = vec![
create_test_allocation(
0x1000,
1024,
Some("var1".to_string()),
Some("i32".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x2000,
512,
Some("var2".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
];
let registry = HashMap::new();
let result = build_variable_relationship_graph(&allocations, ®istry);
assert!(result.is_ok());
let graph = result.unwrap();
assert_eq!(graph.nodes.len(), 2);
assert!(!graph.relationships.is_empty());
assert!(!graph.clusters.is_empty());
}
#[test]
fn test_comprehensive_workflow() {
let allocations = vec![
create_test_allocation(
0x1000,
1024,
Some("main_var1".to_string()),
Some("i32".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x2000,
512,
Some("main_var2".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x3000,
256,
Some("func_var".to_string()),
Some("f64".to_string()),
Some("function".to_string()),
),
create_smart_pointer_allocation(
0x4000,
64,
"rc1".to_string(),
"Rc<Data>".to_string(),
SmartPointerType::Rc,
0x6000,
vec![0x5000],
None,
),
create_smart_pointer_allocation(
0x5000,
64,
"rc2".to_string(),
"Rc<Data>".to_string(),
SmartPointerType::Rc,
0x6000,
vec![],
Some(0x4000),
),
create_smart_pointer_allocation(
0x7000,
64,
"box_ptr".to_string(),
"Box<String>".to_string(),
SmartPointerType::Box,
0x8000,
vec![],
None,
),
create_test_allocation(
0x8000,
32,
Some("boxed_data".to_string()),
Some("String".to_string()),
Some("main".to_string()),
),
create_test_allocation(
0x9000,
128,
Some("vec_data".to_string()),
Some("Vec<i32>".to_string()),
Some("main".to_string()),
),
];
let mut registry = HashMap::new();
registry.insert(
0x1000,
create_test_variable_info("registry_main_var".to_string(), "u32".to_string()),
);
let graph = VariableRelationshipBuilder::new()
.add_allocations(&allocations, ®istry)
.detect_references()
.detect_scope_relationships()
.detect_circular_references()
.build_graph();
assert_eq!(graph.nodes.len(), 8);
assert!(!graph.relationships.is_empty());
assert!(!graph.clusters.is_empty());
let has_clones = graph
.relationships
.iter()
.any(|rel| rel.relationship_type == RelationshipType::Clones);
let has_ownership = graph
.relationships
.iter()
.any(|rel| rel.relationship_type == RelationshipType::Owns);
let has_containment = graph
.relationships
.iter()
.any(|rel| rel.relationship_type == RelationshipType::Contains);
assert!(has_clones || has_ownership || has_containment);
let mut categories = Vec::new();
for node in &graph.nodes {
if !categories.contains(&&node.category) {
categories.push(&node.category);
}
}
assert!(
!categories.is_empty(),
"Expected at least one variable category"
);
let has_user_vars = categories.contains(&&VariableCategory::UserVariable);
assert!(has_user_vars, "Expected at least one user variable");
let stats = &graph.statistics;
assert_eq!(stats.total_nodes, 8);
assert!(stats.total_relationships > 0);
assert!(stats.avg_relationships_per_node >= 0.0);
assert!(graph.metadata.contains_key("build_timestamp"));
let scope_clusters: Vec<_> = graph
.clusters
.iter()
.filter(|cluster| cluster.cluster_type == ClusterType::Scope)
.collect();
assert!(!scope_clusters.is_empty());
let main_cluster = scope_clusters
.iter()
.find(|cluster| cluster.variables.len() > 1);
assert!(main_cluster.is_some());
}
#[test]
fn test_circular_reference_relationships_use_ptr_not_stack_address() {
let allocations = vec![
create_smart_pointer_allocation(
0x1000,
64,
"rc1".to_string(),
"Rc<Data>".to_string(),
SmartPointerType::Rc,
0x5000,
vec![0x2000],
None,
),
create_smart_pointer_allocation(
0x2000,
64,
"rc2".to_string(),
"Rc<Data>".to_string(),
SmartPointerType::Rc,
0x5000,
vec![],
Some(0x1000),
),
];
let registry = HashMap::new();
let graph = VariableRelationshipBuilder::new()
.add_allocations(&allocations, ®istry)
.detect_references()
.detect_circular_references()
.build_graph();
let circular_rels: Vec<_> = graph
.relationships
.iter()
.filter(|rel| {
rel.metadata
.get("circular_reference")
.and_then(|v| v.as_bool())
.unwrap_or(false)
})
.collect();
for rel in &circular_rels {
assert!(
rel.source.starts_with("0x"),
"Circular reference source should use pointer format '0x...', got: {}",
rel.source
);
assert!(
rel.target.starts_with("0x"),
"Circular reference target should use pointer format '0x...', got: {}",
rel.target
);
let source_addr: usize =
usize::from_str_radix(rel.source.trim_start_matches("0x"), 16).unwrap();
let target_addr: usize =
usize::from_str_radix(rel.target.trim_start_matches("0x"), 16).unwrap();
assert!(
source_addr == 0x1000 || source_addr == 0x2000,
"Circular reference source should be 0x1000 or 0x2000, got: {:#x}",
source_addr
);
assert!(
target_addr == 0x1000 || target_addr == 0x2000,
"Circular reference target should be 0x1000 or 0x2000, got: {:#x}",
target_addr
);
}
}
}