borrowscope_runtime/
lifetime.rs

1//! Lifetime tracking and analysis.
2//!
3//! This module provides utilities for analyzing lifetime relationships from runtime events.
4//! Since Rust lifetimes are a compile-time concept, this module approximates them by
5//! tracking borrow start/end times and detecting overlaps.
6//!
7//! # Key Types
8//!
9//! - [`LifetimeRelation`] - A borrow relationship with start/end times
10//! - [`Timeline`] - Chronological view of all lifetime relations
11//! - [`ElisionRule`] - Lifetime elision rules for educational purposes
12
13use crate::event::Event;
14use serde::{Deserialize, Serialize};
15
16/// A lifetime relationship between a borrower and borrowed variable.
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct LifetimeRelation {
19    /// ID of the borrowing variable
20    pub borrower_id: String,
21    /// ID of the borrowed variable
22    pub borrowed_id: String,
23    /// Timestamp when borrow started
24    pub start_time: u64,
25    /// Timestamp when borrow ended (None if still active)
26    pub end_time: Option<u64>,
27    /// Whether this is a mutable borrow
28    pub is_mutable: bool,
29}
30
31impl LifetimeRelation {
32    /// Create a new lifetime relation.
33    pub fn new(
34        borrower_id: String,
35        borrowed_id: String,
36        start_time: u64,
37        is_mutable: bool,
38    ) -> Self {
39        Self {
40            borrower_id,
41            borrowed_id,
42            start_time,
43            end_time: None,
44            is_mutable,
45        }
46    }
47
48    /// Check if this lifetime is still active (not yet ended).
49    pub fn is_active(&self) -> bool {
50        self.end_time.is_none()
51    }
52
53    /// Get the duration of this lifetime in timestamp units.
54    pub fn duration(&self) -> Option<u64> {
55        self.end_time.map(|end| end - self.start_time)
56    }
57
58    /// Check if this lifetime overlaps with another.
59    ///
60    /// Useful for detecting potential borrow conflicts.
61    pub fn overlaps_with(&self, other: &LifetimeRelation) -> bool {
62        let self_end = self.end_time.unwrap_or(u64::MAX);
63        let other_end = other.end_time.unwrap_or(u64::MAX);
64
65        self.start_time < other_end && other.start_time < self_end
66    }
67}
68
69/// Timeline representation for visualization.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Timeline {
72    /// All lifetime relations in chronological order
73    pub relations: Vec<LifetimeRelation>,
74    /// Minimum timestamp
75    pub min_time: u64,
76    /// Maximum timestamp
77    pub max_time: u64,
78}
79
80impl Timeline {
81    /// Create a timeline from events
82    pub fn from_events(events: &[Event]) -> Self {
83        let mut relations = Vec::new();
84        let mut active_borrows: std::collections::HashMap<String, (String, u64, bool)> =
85            std::collections::HashMap::new();
86
87        let mut min_time = u64::MAX;
88        let mut max_time = 0;
89
90        for event in events {
91            let timestamp = event.timestamp();
92            min_time = min_time.min(timestamp);
93            max_time = max_time.max(timestamp);
94
95            match event {
96                Event::Borrow {
97                    borrower_id,
98                    owner_id,
99                    mutable,
100                    timestamp,
101                    ..
102                } => {
103                    active_borrows.insert(
104                        borrower_id.clone(),
105                        (owner_id.clone(), *timestamp, *mutable),
106                    );
107                }
108                Event::Drop { var_id, timestamp } => {
109                    if let Some((borrowed_id, start_time, is_mutable)) =
110                        active_borrows.remove(var_id)
111                    {
112                        let mut relation = LifetimeRelation::new(
113                            var_id.clone(),
114                            borrowed_id,
115                            start_time,
116                            is_mutable,
117                        );
118                        relation.end_time = Some(*timestamp);
119                        relations.push(relation);
120                    }
121                }
122                _ => {}
123            }
124        }
125
126        // Add any still-active borrows
127        for (borrower_id, (borrowed_id, start_time, is_mutable)) in active_borrows {
128            relations.push(LifetimeRelation::new(
129                borrower_id,
130                borrowed_id,
131                start_time,
132                is_mutable,
133            ));
134        }
135
136        Self {
137            relations,
138            min_time: if min_time == u64::MAX { 0 } else { min_time },
139            max_time,
140        }
141    }
142
143    /// Get all relations for a specific variable
144    pub fn relations_for(&self, var_id: &str) -> Vec<&LifetimeRelation> {
145        self.relations
146            .iter()
147            .filter(|r| r.borrower_id == var_id || r.borrowed_id == var_id)
148            .collect()
149    }
150
151    /// Get all active relations at a specific timestamp
152    pub fn active_at(&self, timestamp: u64) -> Vec<&LifetimeRelation> {
153        self.relations
154            .iter()
155            .filter(|r| r.start_time <= timestamp && r.end_time.map_or(true, |end| timestamp < end))
156            .collect()
157    }
158
159    /// Check if two variables have overlapping lifetimes
160    pub fn lifetimes_overlap(&self, var1: &str, var2: &str) -> bool {
161        let relations1 = self.relations_for(var1);
162        let relations2 = self.relations_for(var2);
163
164        for r1 in &relations1 {
165            for r2 in &relations2 {
166                if r1.overlaps_with(r2) {
167                    return true;
168                }
169            }
170        }
171        false
172    }
173
174    /// Get the total duration of the timeline
175    pub fn total_duration(&self) -> u64 {
176        self.max_time.saturating_sub(self.min_time)
177    }
178}
179
180/// Lifetime elision rules (for documentation and analysis)
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum ElisionRule {
183    /// Each input parameter gets its own lifetime
184    EachInputOwn,
185    /// If one input, output gets that lifetime
186    SingleInputToOutput,
187    /// If multiple inputs with &self, output gets self's lifetime
188    SelfToOutput,
189}
190
191impl ElisionRule {
192    /// Get a description of the elision rule
193    pub fn description(&self) -> &'static str {
194        match self {
195            ElisionRule::EachInputOwn => "Each input parameter gets its own lifetime",
196            ElisionRule::SingleInputToOutput => "If one input parameter, output gets that lifetime",
197            ElisionRule::SelfToOutput => {
198                "If multiple inputs with &self, output gets self's lifetime"
199            }
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_lifetime_relation_creation() {
210        let relation = LifetimeRelation::new("r".to_string(), "x".to_string(), 100, false);
211
212        assert_eq!(relation.borrower_id, "r");
213        assert_eq!(relation.borrowed_id, "x");
214        assert_eq!(relation.start_time, 100);
215        assert_eq!(relation.end_time, None);
216        assert!(!relation.is_mutable);
217        assert!(relation.is_active());
218    }
219
220    #[test]
221    fn test_lifetime_relation_duration() {
222        let mut relation = LifetimeRelation::new("r".to_string(), "x".to_string(), 100, false);
223
224        assert_eq!(relation.duration(), None);
225
226        relation.end_time = Some(150);
227        assert_eq!(relation.duration(), Some(50));
228        assert!(!relation.is_active());
229    }
230
231    #[test]
232    fn test_lifetime_overlap() {
233        let r1 = LifetimeRelation {
234            borrower_id: "r1".to_string(),
235            borrowed_id: "x".to_string(),
236            start_time: 100,
237            end_time: Some(200),
238            is_mutable: false,
239        };
240
241        let r2 = LifetimeRelation {
242            borrower_id: "r2".to_string(),
243            borrowed_id: "x".to_string(),
244            start_time: 150,
245            end_time: Some(250),
246            is_mutable: false,
247        };
248
249        let r3 = LifetimeRelation {
250            borrower_id: "r3".to_string(),
251            borrowed_id: "x".to_string(),
252            start_time: 300,
253            end_time: Some(400),
254            is_mutable: false,
255        };
256
257        assert!(r1.overlaps_with(&r2));
258        assert!(r2.overlaps_with(&r1));
259        assert!(!r1.overlaps_with(&r3));
260        assert!(!r3.overlaps_with(&r1));
261    }
262
263    #[test]
264    fn test_timeline_from_events() {
265        let events = vec![
266            Event::New {
267                timestamp: 0,
268                var_name: "x".to_string(),
269                var_id: "x_0".to_string(),
270                type_name: "i32".to_string(),
271            },
272            Event::Borrow {
273                timestamp: 10,
274                borrower_name: "r1".to_string(),
275                borrower_id: "r1_0".to_string(),
276                owner_id: "x_0".to_string(),
277                mutable: false,
278            },
279            Event::Borrow {
280                timestamp: 20,
281                borrower_name: "r2".to_string(),
282                borrower_id: "r2_0".to_string(),
283                owner_id: "x_0".to_string(),
284                mutable: false,
285            },
286            Event::Drop {
287                timestamp: 30,
288                var_id: "r2_0".to_string(),
289            },
290            Event::Drop {
291                timestamp: 40,
292                var_id: "r1_0".to_string(),
293            },
294            Event::Drop {
295                timestamp: 50,
296                var_id: "x_0".to_string(),
297            },
298        ];
299
300        let timeline = Timeline::from_events(&events);
301
302        assert_eq!(timeline.relations.len(), 2);
303        assert_eq!(timeline.min_time, 0);
304        assert_eq!(timeline.max_time, 50);
305        assert_eq!(timeline.total_duration(), 50);
306
307        // Check r1 relation
308        let r1_relation = timeline
309            .relations
310            .iter()
311            .find(|r| r.borrower_id == "r1_0")
312            .unwrap();
313        assert_eq!(r1_relation.borrowed_id, "x_0");
314        assert_eq!(r1_relation.start_time, 10);
315        assert_eq!(r1_relation.end_time, Some(40));
316        assert_eq!(r1_relation.duration(), Some(30));
317
318        // Check r2 relation
319        let r2_relation = timeline
320            .relations
321            .iter()
322            .find(|r| r.borrower_id == "r2_0")
323            .unwrap();
324        assert_eq!(r2_relation.borrowed_id, "x_0");
325        assert_eq!(r2_relation.start_time, 20);
326        assert_eq!(r2_relation.end_time, Some(30));
327        assert_eq!(r2_relation.duration(), Some(10));
328    }
329
330    #[test]
331    fn test_timeline_relations_for() {
332        let events = vec![
333            Event::New {
334                timestamp: 0,
335                var_name: "x".to_string(),
336                var_id: "x_0".to_string(),
337                type_name: "i32".to_string(),
338            },
339            Event::Borrow {
340                timestamp: 10,
341                borrower_name: "r".to_string(),
342                borrower_id: "r_0".to_string(),
343                owner_id: "x_0".to_string(),
344                mutable: false,
345            },
346            Event::Drop {
347                timestamp: 20,
348                var_id: "r_0".to_string(),
349            },
350        ];
351
352        let timeline = Timeline::from_events(&events);
353
354        let x_relations = timeline.relations_for("x_0");
355        assert_eq!(x_relations.len(), 1);
356        assert_eq!(x_relations[0].borrowed_id, "x_0");
357
358        let r_relations = timeline.relations_for("r_0");
359        assert_eq!(r_relations.len(), 1);
360        assert_eq!(r_relations[0].borrower_id, "r_0");
361    }
362
363    #[test]
364    fn test_timeline_active_at() {
365        let events = vec![
366            Event::Borrow {
367                timestamp: 10,
368                borrower_name: "r1".to_string(),
369                borrower_id: "r1_0".to_string(),
370                owner_id: "x_0".to_string(),
371                mutable: false,
372            },
373            Event::Borrow {
374                timestamp: 20,
375                borrower_name: "r2".to_string(),
376                borrower_id: "r2_0".to_string(),
377                owner_id: "x_0".to_string(),
378                mutable: false,
379            },
380            Event::Drop {
381                timestamp: 30,
382                var_id: "r1_0".to_string(),
383            },
384        ];
385
386        let timeline = Timeline::from_events(&events);
387
388        // At time 15, only r1 is active
389        let active_at_15 = timeline.active_at(15);
390        assert_eq!(active_at_15.len(), 1);
391        assert_eq!(active_at_15[0].borrower_id, "r1_0");
392
393        // At time 25, both r1 and r2 are active
394        let active_at_25 = timeline.active_at(25);
395        assert_eq!(active_at_25.len(), 2);
396
397        // At time 35, only r2 is active (r1 dropped at 30)
398        let active_at_35 = timeline.active_at(35);
399        assert_eq!(active_at_35.len(), 1);
400        assert_eq!(active_at_35[0].borrower_id, "r2_0");
401    }
402
403    #[test]
404    fn test_timeline_lifetimes_overlap() {
405        let events = vec![
406            Event::Borrow {
407                timestamp: 10,
408                borrower_name: "r1".to_string(),
409                borrower_id: "r1_0".to_string(),
410                owner_id: "x_0".to_string(),
411                mutable: false,
412            },
413            Event::Borrow {
414                timestamp: 20,
415                borrower_name: "r2".to_string(),
416                borrower_id: "r2_0".to_string(),
417                owner_id: "x_0".to_string(),
418                mutable: false,
419            },
420            Event::Drop {
421                timestamp: 30,
422                var_id: "r1_0".to_string(),
423            },
424            Event::Drop {
425                timestamp: 40,
426                var_id: "r2_0".to_string(),
427            },
428        ];
429
430        let timeline = Timeline::from_events(&events);
431
432        // r1 and r2 overlap (r1: 10-30, r2: 20-40)
433        assert!(timeline.lifetimes_overlap("r1_0", "r2_0"));
434    }
435
436    #[test]
437    fn test_mutable_borrow_tracking() {
438        let events = vec![
439            Event::Borrow {
440                timestamp: 10,
441                borrower_name: "r".to_string(),
442                borrower_id: "r_0".to_string(),
443                owner_id: "x_0".to_string(),
444                mutable: true,
445            },
446            Event::Drop {
447                timestamp: 20,
448                var_id: "r_0".to_string(),
449            },
450        ];
451
452        let timeline = Timeline::from_events(&events);
453
454        assert_eq!(timeline.relations.len(), 1);
455        assert!(timeline.relations[0].is_mutable);
456    }
457
458    #[test]
459    fn test_elision_rule_descriptions() {
460        assert!(!ElisionRule::EachInputOwn.description().is_empty());
461        assert!(!ElisionRule::SingleInputToOutput.description().is_empty());
462        assert!(!ElisionRule::SelfToOutput.description().is_empty());
463    }
464
465    #[test]
466    fn test_empty_timeline() {
467        let timeline = Timeline::from_events(&[]);
468
469        assert_eq!(timeline.relations.len(), 0);
470        assert_eq!(timeline.min_time, 0);
471        assert_eq!(timeline.max_time, 0);
472        assert_eq!(timeline.total_duration(), 0);
473    }
474
475    #[test]
476    fn test_nested_lifetimes() {
477        let events = vec![
478            Event::Borrow {
479                timestamp: 10,
480                borrower_name: "r1".to_string(),
481                borrower_id: "r1_0".to_string(),
482                owner_id: "x_0".to_string(),
483                mutable: false,
484            },
485            Event::Borrow {
486                timestamp: 15,
487                borrower_name: "r2".to_string(),
488                borrower_id: "r2_0".to_string(),
489                owner_id: "x_0".to_string(),
490                mutable: false,
491            },
492            Event::Drop {
493                timestamp: 20,
494                var_id: "r2_0".to_string(),
495            },
496            Event::Drop {
497                timestamp: 25,
498                var_id: "r1_0".to_string(),
499            },
500        ];
501
502        let timeline = Timeline::from_events(&events);
503
504        // r2's lifetime (15-20) is nested within r1's lifetime (10-25)
505        let r1 = timeline
506            .relations
507            .iter()
508            .find(|r| r.borrower_id == "r1_0")
509            .unwrap();
510        let r2 = timeline
511            .relations
512            .iter()
513            .find(|r| r.borrower_id == "r2_0")
514            .unwrap();
515
516        assert!(r1.start_time < r2.start_time);
517        assert!(r1.end_time.unwrap() > r2.end_time.unwrap());
518        assert!(r1.overlaps_with(r2));
519    }
520}