borrowscope_runtime/
graph.rs

1//! Ownership graph data structures and analysis.
2//!
3//! This module provides graph-based representations of ownership relationships
4//! built from event streams. Use [`build_graph`] to construct a graph from events.
5//!
6//! # Example
7//!
8//! ```rust
9//! # use borrowscope_runtime::*;
10//! # reset();
11//! let x = track_new("x", 42);
12//! let r = track_borrow("r", &x);
13//!
14//! let graph = get_graph();
15//! assert!(!graph.nodes.is_empty());
16//! ```
17
18use crate::event::Event;
19use crate::lifetime::{LifetimeRelation, Timeline};
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22
23/// A variable node in the ownership graph.
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct Variable {
26    /// Unique identifier
27    pub id: String,
28    /// Human-readable name
29    pub name: String,
30    /// Type from `std::any::type_name`
31    pub type_name: String,
32    /// Timestamp when created
33    pub created_at: u64,
34    /// Timestamp when dropped (if dropped)
35    pub dropped_at: Option<u64>,
36}
37
38/// A relationship edge between variables.
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[serde(tag = "kind")]
41pub enum Relationship {
42    /// Ownership relationship
43    Owns { from: String, to: String },
44    /// Immutable borrow (`&T`)
45    BorrowsImmut {
46        from: String,
47        to: String,
48        start: u64,
49        end: u64,
50    },
51    /// Mutable borrow (`&mut T`)
52    BorrowsMut {
53        from: String,
54        to: String,
55        start: u64,
56        end: u64,
57    },
58}
59
60/// The complete ownership graph built from events.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct OwnershipGraph {
63    /// Variable nodes
64    pub nodes: Vec<Variable>,
65    /// Relationship edges
66    pub edges: Vec<Relationship>,
67}
68
69impl OwnershipGraph {
70    /// Create an empty graph.
71    pub fn new() -> Self {
72        Self {
73            nodes: Vec::new(),
74            edges: Vec::new(),
75        }
76    }
77
78    /// Add a variable node.
79    pub fn add_variable(&mut self, var: Variable) {
80        self.nodes.push(var);
81    }
82
83    /// Add a relationship edge.
84    pub fn add_relationship(&mut self, rel: Relationship) {
85        self.edges.push(rel);
86    }
87
88    /// Find a variable by ID.
89    pub fn find_variable(&self, id: &str) -> Option<&Variable> {
90        self.nodes.iter().find(|v| v.id == id)
91    }
92
93    /// Find all borrows of a variable.
94    pub fn find_borrows(&self, var_id: &str) -> Vec<&Relationship> {
95        self.edges
96            .iter()
97            .filter(|rel| match rel {
98                Relationship::BorrowsImmut { to, .. } | Relationship::BorrowsMut { to, .. } => {
99                    to == var_id
100                }
101                _ => false,
102            })
103            .collect()
104    }
105
106    /// Get statistics
107    pub fn stats(&self) -> GraphStats {
108        let mut immut_borrows = 0;
109        let mut mut_borrows = 0;
110
111        for edge in &self.edges {
112            match edge {
113                Relationship::BorrowsImmut { .. } => immut_borrows += 1,
114                Relationship::BorrowsMut { .. } => mut_borrows += 1,
115                _ => {}
116            }
117        }
118
119        GraphStats {
120            total_variables: self.nodes.len(),
121            total_relationships: self.edges.len(),
122            immutable_borrows: immut_borrows,
123            mutable_borrows: mut_borrows,
124        }
125    }
126
127    /// Get lifetime relations from the graph
128    pub fn lifetime_relations(&self, events: &[Event]) -> Vec<LifetimeRelation> {
129        Timeline::from_events(events).relations
130    }
131
132    /// Create a timeline visualization from events
133    pub fn create_timeline(&self, events: &[Event]) -> Timeline {
134        Timeline::from_events(events)
135    }
136
137    /// Get all active borrows at a specific timestamp
138    pub fn active_borrows_at(&self, events: &[Event], timestamp: u64) -> Vec<LifetimeRelation> {
139        let timeline = Timeline::from_events(events);
140        timeline.active_at(timestamp).into_iter().cloned().collect()
141    }
142
143    /// Check if two variables have overlapping lifetimes
144    pub fn lifetimes_overlap(&self, events: &[Event], var1: &str, var2: &str) -> bool {
145        let timeline = Timeline::from_events(events);
146        timeline.lifetimes_overlap(var1, var2)
147    }
148}
149
150impl Default for OwnershipGraph {
151    fn default() -> Self {
152        Self::new()
153    }
154}
155
156/// Graph statistics
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct GraphStats {
159    pub total_variables: usize,
160    pub total_relationships: usize,
161    pub immutable_borrows: usize,
162    pub mutable_borrows: usize,
163}
164
165/// Build a graph from events
166pub fn build_graph(events: &[Event]) -> OwnershipGraph {
167    let mut graph = OwnershipGraph::new();
168    let mut var_map: HashMap<String, Variable> = HashMap::new();
169    let mut borrow_map: HashMap<String, (String, bool, u64)> = HashMap::new();
170
171    for event in events {
172        match event {
173            Event::New {
174                var_name,
175                var_id,
176                type_name,
177                timestamp,
178            } => {
179                let var = Variable {
180                    id: var_id.clone(),
181                    name: var_name.clone(),
182                    type_name: type_name.clone(),
183                    created_at: *timestamp,
184                    dropped_at: None,
185                };
186                var_map.insert(var_id.clone(), var);
187            }
188
189            Event::Borrow {
190                borrower_id,
191                owner_id,
192                mutable,
193                timestamp,
194                ..
195            } => {
196                borrow_map.insert(
197                    borrower_id.clone(),
198                    (owner_id.clone(), *mutable, *timestamp),
199                );
200            }
201
202            Event::Drop { var_id, timestamp } => {
203                // Mark variable as dropped
204                if let Some(var) = var_map.get_mut(var_id) {
205                    var.dropped_at = Some(*timestamp);
206                }
207
208                // End borrow if this is a borrower
209                if let Some((owner_id, is_mutable, start)) = borrow_map.remove(var_id) {
210                    let rel = if is_mutable {
211                        Relationship::BorrowsMut {
212                            from: var_id.clone(),
213                            to: owner_id,
214                            start,
215                            end: *timestamp,
216                        }
217                    } else {
218                        Relationship::BorrowsImmut {
219                            from: var_id.clone(),
220                            to: owner_id,
221                            start,
222                            end: *timestamp,
223                        }
224                    };
225                    graph.add_relationship(rel);
226                }
227            }
228
229            _ => {}
230        }
231    }
232
233    // Add all variables to graph
234    for var in var_map.into_values() {
235        graph.add_variable(var);
236    }
237
238    graph
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_empty_graph() {
247        let graph = OwnershipGraph::new();
248        assert_eq!(graph.nodes.len(), 0);
249        assert_eq!(graph.edges.len(), 0);
250    }
251
252    #[test]
253    fn test_add_variable() {
254        let mut graph = OwnershipGraph::new();
255
256        let var = Variable {
257            id: "x_0".to_string(),
258            name: "x".to_string(),
259            type_name: "i32".to_string(),
260            created_at: 1,
261            dropped_at: None,
262        };
263
264        graph.add_variable(var);
265        assert_eq!(graph.nodes.len(), 1);
266    }
267
268    #[test]
269    fn test_add_relationship() {
270        let mut graph = OwnershipGraph::new();
271
272        let rel = Relationship::BorrowsImmut {
273            from: "r_1".to_string(),
274            to: "x_0".to_string(),
275            start: 2,
276            end: 3,
277        };
278
279        graph.add_relationship(rel);
280        assert_eq!(graph.edges.len(), 1);
281    }
282
283    #[test]
284    fn test_find_variable() {
285        let mut graph = OwnershipGraph::new();
286
287        let var = Variable {
288            id: "x_0".to_string(),
289            name: "x".to_string(),
290            type_name: "i32".to_string(),
291            created_at: 1,
292            dropped_at: None,
293        };
294
295        graph.add_variable(var);
296
297        let found = graph.find_variable("x_0");
298        assert!(found.is_some());
299        assert_eq!(found.unwrap().name, "x");
300
301        let not_found = graph.find_variable("y_0");
302        assert!(not_found.is_none());
303    }
304
305    #[test]
306    fn test_find_borrows() {
307        let mut graph = OwnershipGraph::new();
308
309        graph.add_relationship(Relationship::BorrowsImmut {
310            from: "r_1".to_string(),
311            to: "x_0".to_string(),
312            start: 2,
313            end: 3,
314        });
315
316        graph.add_relationship(Relationship::BorrowsMut {
317            from: "s_2".to_string(),
318            to: "x_0".to_string(),
319            start: 4,
320            end: 5,
321        });
322
323        let borrows = graph.find_borrows("x_0");
324        assert_eq!(borrows.len(), 2);
325    }
326
327    #[test]
328    fn test_stats() {
329        let mut graph = OwnershipGraph::new();
330
331        graph.add_variable(Variable {
332            id: "x_0".to_string(),
333            name: "x".to_string(),
334            type_name: "i32".to_string(),
335            created_at: 1,
336            dropped_at: None,
337        });
338
339        graph.add_relationship(Relationship::BorrowsImmut {
340            from: "r_1".to_string(),
341            to: "x_0".to_string(),
342            start: 2,
343            end: 3,
344        });
345
346        graph.add_relationship(Relationship::BorrowsMut {
347            from: "s_2".to_string(),
348            to: "x_0".to_string(),
349            start: 4,
350            end: 5,
351        });
352
353        let stats = graph.stats();
354        assert_eq!(stats.total_variables, 1);
355        assert_eq!(stats.total_relationships, 2);
356        assert_eq!(stats.immutable_borrows, 1);
357        assert_eq!(stats.mutable_borrows, 1);
358    }
359
360    #[test]
361    fn test_build_graph_simple() {
362        let events = vec![
363            Event::New {
364                timestamp: 1,
365                var_name: "x".to_string(),
366                var_id: "x_0".to_string(),
367                type_name: "i32".to_string(),
368            },
369            Event::Drop {
370                timestamp: 2,
371                var_id: "x_0".to_string(),
372            },
373        ];
374
375        let graph = build_graph(&events);
376        assert_eq!(graph.nodes.len(), 1);
377        assert_eq!(graph.nodes[0].dropped_at, Some(2));
378    }
379
380    #[test]
381    fn test_build_graph_with_borrow() {
382        let events = vec![
383            Event::New {
384                timestamp: 1,
385                var_name: "x".to_string(),
386                var_id: "x_0".to_string(),
387                type_name: "i32".to_string(),
388            },
389            Event::Borrow {
390                timestamp: 2,
391                borrower_name: "r".to_string(),
392                borrower_id: "r_1".to_string(),
393                owner_id: "x_0".to_string(),
394                mutable: false,
395            },
396            Event::Drop {
397                timestamp: 3,
398                var_id: "r_1".to_string(),
399            },
400            Event::Drop {
401                timestamp: 4,
402                var_id: "x_0".to_string(),
403            },
404        ];
405
406        let graph = build_graph(&events);
407        assert_eq!(graph.nodes.len(), 1);
408        assert_eq!(graph.edges.len(), 1);
409
410        match &graph.edges[0] {
411            Relationship::BorrowsImmut {
412                from,
413                to,
414                start,
415                end,
416            } => {
417                assert_eq!(from, "r_1");
418                assert_eq!(to, "x_0");
419                assert_eq!(*start, 2);
420                assert_eq!(*end, 3);
421            }
422            _ => panic!("Expected BorrowsImmut"),
423        }
424    }
425
426    #[test]
427    fn test_build_graph_mutable_borrow() {
428        let events = vec![
429            Event::New {
430                timestamp: 1,
431                var_name: "x".to_string(),
432                var_id: "x_0".to_string(),
433                type_name: "Vec<i32>".to_string(),
434            },
435            Event::Borrow {
436                timestamp: 2,
437                borrower_name: "r".to_string(),
438                borrower_id: "r_1".to_string(),
439                owner_id: "x_0".to_string(),
440                mutable: true,
441            },
442            Event::Drop {
443                timestamp: 3,
444                var_id: "r_1".to_string(),
445            },
446        ];
447
448        let graph = build_graph(&events);
449        assert_eq!(graph.edges.len(), 1);
450
451        match &graph.edges[0] {
452            Relationship::BorrowsMut { .. } => {}
453            _ => panic!("Expected BorrowsMut"),
454        }
455    }
456
457    #[test]
458    fn test_serialization() {
459        let graph = OwnershipGraph {
460            nodes: vec![Variable {
461                id: "x_0".to_string(),
462                name: "x".to_string(),
463                type_name: "i32".to_string(),
464                created_at: 1,
465                dropped_at: Some(2),
466            }],
467            edges: vec![Relationship::BorrowsImmut {
468                from: "r_1".to_string(),
469                to: "x_0".to_string(),
470                start: 2,
471                end: 3,
472            }],
473        };
474
475        let json = serde_json::to_string(&graph).unwrap();
476        let deserialized: OwnershipGraph = serde_json::from_str(&json).unwrap();
477
478        assert_eq!(deserialized.nodes.len(), 1);
479        assert_eq!(deserialized.edges.len(), 1);
480    }
481}