1use crate::core::types::{AllocationInfo, SmartPointerInfo, SmartPointerType};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct CircularReference {
13 pub cycle_path: Vec<CircularReferenceNode>,
15
16 pub suggested_weak_positions: Vec<usize>,
18
19 pub estimated_leaked_memory: usize,
21
22 pub severity: CircularReferenceSeverity,
24
25 pub cycle_type: CircularReferenceType,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct CircularReferenceNode {
32 pub ptr: usize,
34
35 pub data_ptr: usize,
37
38 pub var_name: Option<String>,
40
41 pub type_name: Option<String>,
43
44 pub pointer_type: SmartPointerType,
46
47 pub ref_count: usize,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub enum CircularReferenceSeverity {
54 Low,
56 Medium,
58 High,
60 Critical,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub enum CircularReferenceType {
67 Simple,
69 Complex,
71 SelfReference,
73 Nested,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct CircularReferenceAnalysis {
80 pub circular_references: Vec<CircularReference>,
82
83 pub total_smart_pointers: usize,
85
86 pub pointers_in_cycles: usize,
88
89 pub total_leaked_memory: usize,
91
92 pub statistics: CircularReferenceStatistics,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct CircularReferenceStatistics {
99 pub by_severity: HashMap<String, usize>,
101
102 pub by_type: HashMap<String, usize>,
104
105 pub by_pointer_type: HashMap<String, usize>,
107
108 pub average_cycle_length: f64,
110
111 pub largest_cycle_size: usize,
113}
114
115#[derive(Debug)]
117struct ReferenceGraph {
118 adjacency: HashMap<usize, Vec<usize>>,
120
121 reverse_refs: HashMap<usize, Vec<usize>>,
123
124 smart_pointers: HashMap<usize, SmartPointerInfo>,
126
127 allocations: HashMap<usize, AllocationInfo>,
129}
130
131impl ReferenceGraph {
132 fn new(allocations: &[AllocationInfo]) -> Self {
134 let mut graph = ReferenceGraph {
135 adjacency: HashMap::new(),
136 reverse_refs: HashMap::new(),
137 smart_pointers: HashMap::new(),
138 allocations: HashMap::new(),
139 };
140
141 for allocation in allocations {
143 if let Some(ref smart_info) = allocation.smart_pointer_info {
144 if smart_info.is_weak_reference {
146 continue;
147 }
148
149 graph
150 .smart_pointers
151 .insert(allocation.ptr, smart_info.clone());
152 graph.allocations.insert(allocation.ptr, allocation.clone());
153
154 graph
156 .adjacency
157 .entry(allocation.ptr)
158 .or_insert_with(Vec::new);
159
160 graph
162 .reverse_refs
163 .entry(smart_info.data_ptr)
164 .or_insert_with(Vec::new)
165 .push(allocation.ptr);
166
167 for &clone_ptr in &smart_info.clones {
169 graph
170 .adjacency
171 .entry(allocation.ptr)
172 .or_insert_with(Vec::new)
173 .push(clone_ptr);
174 }
175
176 if let Some(source_ptr) = smart_info.cloned_from {
178 graph
179 .adjacency
180 .entry(source_ptr)
181 .or_insert_with(Vec::new)
182 .push(allocation.ptr);
183 }
184 }
185 }
186
187 graph
188 }
189
190 fn detect_cycles(&self) -> Vec<Vec<usize>> {
192 let mut cycles = Vec::new();
193 let mut visited = HashSet::new();
194 let mut rec_stack = HashSet::new();
195 let mut path = Vec::new();
196
197 for &ptr in self.smart_pointers.keys() {
198 if !visited.contains(&ptr) {
199 self.dfs_detect_cycles(ptr, &mut visited, &mut rec_stack, &mut path, &mut cycles);
200 }
201 }
202
203 cycles
204 }
205
206 fn dfs_detect_cycles(
208 &self,
209 ptr: usize,
210 visited: &mut HashSet<usize>,
211 rec_stack: &mut HashSet<usize>,
212 path: &mut Vec<usize>,
213 cycles: &mut Vec<Vec<usize>>,
214 ) {
215 visited.insert(ptr);
216 rec_stack.insert(ptr);
217 path.push(ptr);
218
219 if let Some(neighbors) = self.adjacency.get(&ptr) {
220 for &neighbor in neighbors {
221 if !visited.contains(&neighbor) {
222 self.dfs_detect_cycles(neighbor, visited, rec_stack, path, cycles);
223 } else if rec_stack.contains(&neighbor) {
224 if let Some(cycle_start) = path.iter().position(|&x| x == neighbor) {
226 let cycle = path[cycle_start..].to_vec();
227 cycles.push(cycle);
228 }
229 }
230 }
231 }
232
233 path.pop();
234 rec_stack.remove(&ptr);
235 }
236}
237
238pub fn detect_circular_references(allocations: &[AllocationInfo]) -> CircularReferenceAnalysis {
240 let graph = ReferenceGraph::new(allocations);
241 let raw_cycles = graph.detect_cycles();
242
243 let mut circular_references = Vec::new();
244 let mut total_leaked_memory = 0;
245 let mut pointers_in_cycles = HashSet::new();
246
247 for cycle_path in raw_cycles {
249 if cycle_path.len() < 2 {
250 continue; }
252
253 let circular_ref = analyze_cycle(&cycle_path, &graph);
254 total_leaked_memory += circular_ref.estimated_leaked_memory;
255
256 for node in &circular_ref.cycle_path {
257 pointers_in_cycles.insert(node.ptr);
258 }
259
260 circular_references.push(circular_ref);
261 }
262
263 let statistics = generate_statistics(&circular_references);
265
266 CircularReferenceAnalysis {
267 circular_references,
268 total_smart_pointers: graph.smart_pointers.len(),
269 pointers_in_cycles: pointers_in_cycles.len(),
270 total_leaked_memory,
271 statistics,
272 }
273}
274
275fn analyze_cycle(cycle_path: &[usize], graph: &ReferenceGraph) -> CircularReference {
277 let mut nodes = Vec::new();
278 let mut total_memory = 0;
279
280 for &ptr in cycle_path {
282 if let (Some(smart_info), Some(allocation)) =
283 (graph.smart_pointers.get(&ptr), graph.allocations.get(&ptr))
284 {
285 let node = CircularReferenceNode {
286 ptr,
287 data_ptr: smart_info.data_ptr,
288 var_name: allocation.var_name.clone(),
289 type_name: allocation.type_name.clone(),
290 pointer_type: smart_info.pointer_type.clone(),
291 ref_count: smart_info
292 .latest_ref_counts()
293 .map(|snapshot| snapshot.strong_count)
294 .unwrap_or(1),
295 };
296
297 total_memory += allocation.size;
298 nodes.push(node);
299 }
300 }
301
302 let cycle_type = if cycle_path.len() == 1 {
304 CircularReferenceType::SelfReference
305 } else if cycle_path.len() == 2 {
306 CircularReferenceType::Simple
307 } else {
308 CircularReferenceType::Complex
309 };
310
311 let severity = if total_memory > 1024 * 1024 {
313 CircularReferenceSeverity::Critical
315 } else if total_memory > 64 * 1024 {
316 CircularReferenceSeverity::High
318 } else if total_memory > 4 * 1024 {
319 CircularReferenceSeverity::Medium
321 } else {
322 CircularReferenceSeverity::Low
323 };
324
325 let suggested_weak_positions = suggest_weak_positions(&nodes);
327
328 CircularReference {
329 cycle_path: nodes,
330 suggested_weak_positions,
331 estimated_leaked_memory: total_memory,
332 severity,
333 cycle_type,
334 }
335}
336
337fn suggest_weak_positions(nodes: &[CircularReferenceNode]) -> Vec<usize> {
339 if let Some((index, _)) = nodes
342 .iter()
343 .enumerate()
344 .max_by_key(|(_, node)| node.ref_count)
345 {
346 vec![index]
347 } else {
348 vec![0] }
350}
351
352fn generate_statistics(circular_references: &[CircularReference]) -> CircularReferenceStatistics {
354 let mut by_severity = HashMap::new();
355 let mut by_type = HashMap::new();
356 let mut by_pointer_type = HashMap::new();
357 let mut total_cycle_length = 0;
358 let mut largest_cycle_size = 0;
359
360 for circular_ref in circular_references {
361 let severity_key = format!("{:?}", circular_ref.severity);
363 *by_severity.entry(severity_key).or_insert(0) += 1;
364
365 let type_key = format!("{:?}", circular_ref.cycle_type);
367 *by_type.entry(type_key).or_insert(0) += 1;
368
369 for node in &circular_ref.cycle_path {
371 let pointer_type_key = format!("{:?}", node.pointer_type);
372 *by_pointer_type.entry(pointer_type_key).or_insert(0) += 1;
373 }
374
375 let cycle_length = circular_ref.cycle_path.len();
377 total_cycle_length += cycle_length;
378 largest_cycle_size = largest_cycle_size.max(cycle_length);
379 }
380
381 let average_cycle_length = if circular_references.is_empty() {
382 0.0
383 } else {
384 total_cycle_length as f64 / circular_references.len() as f64
385 };
386
387 CircularReferenceStatistics {
388 by_severity,
389 by_type,
390 by_pointer_type,
391 average_cycle_length,
392 largest_cycle_size,
393 }
394}