borrowscope_runtime/
lib.rs

1#![allow(dead_code)]
2//! # BorrowScope Runtime
3//!
4//! A runtime tracking library for visualizing Rust's ownership and borrowing system.
5//!
6//! This crate captures ownership transfers, borrows, and smart pointer operations
7//! as they happen at runtime, generating structured event data for analysis.
8//!
9//! # Quick Start
10//!
11//! ```rust
12//! use borrowscope_runtime::*;
13//!
14//! // Clear previous tracking data
15//! reset();
16//!
17//! // Track variable creation
18//! let data = track_new("data", vec![1, 2, 3]);
19//!
20//! // Track borrowing
21//! let r = track_borrow("r", &data);
22//! println!("{:?}", r);
23//!
24//! // Track drops
25//! track_drop("r");
26//! track_drop("data");
27//!
28//! // Export events as JSON
29//! let events = get_events();
30//! println!("{}", serde_json::to_string_pretty(&events).unwrap());
31//! ```
32//!
33//! # Feature Flags
34//!
35//! - `track` - Enables runtime tracking. Without this feature, all tracking
36//!   functions compile to no-ops with zero overhead.
37//!
38//! ```toml
39//! [dependencies]
40//! borrowscope-runtime = { version = "0.1", features = ["track"] }
41//! ```
42//!
43//! # Modules
44//!
45//! - Core tracking functions (41 functions for all ownership patterns)
46//! - Event types and serialization
47//! - Ownership graph building and analysis
48//! - JSON export utilities
49//! - Lifetime analysis and timeline construction
50//!
51//! # Tracking Categories
52//!
53//! | Category | Functions |
54//! |----------|-----------|
55//! | Basic ownership | `track_new`, `track_borrow`, `track_borrow_mut`, `track_move`, `track_drop` |
56//! | RAII guards | `track_new_guard`, `track_borrow_guard`, `track_borrow_mut_guard` |
57//! | Smart pointers | `track_rc_new`, `track_rc_clone`, `track_arc_new`, `track_arc_clone` |
58//! | Interior mutability | `track_refcell_*`, `track_cell_*` |
59//! | Unsafe code | `track_raw_ptr*`, `track_unsafe_*`, `track_ffi_call`, `track_transmute` |
60//!
61//! # RAII Guards
62//!
63//! For automatic drop tracking, use the guard variants:
64//!
65//! ```rust
66//! use borrowscope_runtime::*;
67//!
68//! reset();
69//! {
70//!     let data = track_new_guard("data", vec![1, 2, 3]);
71//!     println!("{:?}", *data);
72//!     // track_drop("data") called automatically when data goes out of scope
73//! }
74//!
75//! let events = get_events();
76//! assert!(events.last().unwrap().is_drop());
77//! ```
78//!
79//! # Performance
80//!
81//! - With `track` feature: ~75-80ns per tracking call
82//! - Without `track` feature: zero overhead (compiled away)
83
84mod error;
85mod event;
86mod export;
87mod graph;
88mod guard;
89mod lifetime;
90mod tracker;
91
92#[cfg(test)]
93mod test_utils;
94
95pub use error::{Error, Result};
96pub use event::Event;
97pub use export::{ExportData, ExportEdge, ExportMetadata};
98pub use graph::{build_graph, GraphStats, OwnershipGraph, Relationship, Variable};
99pub use guard::{
100    track_borrow_guard, track_borrow_mut_guard, track_new_guard, BorrowGuard, BorrowMutGuard,
101    TrackGuard,
102};
103pub use lifetime::{ElisionRule, LifetimeRelation, Timeline};
104pub use tracker::{
105    __track_new_with_id_helper, get_borrow_events, get_drop_events, get_event_counts, get_events,
106    get_events_filtered, get_events_for_var, get_move_events, get_new_events, get_summary,
107    print_summary, reset, track_arc_clone, track_arc_clone_with_id, track_arc_new,
108    track_arc_new_with_id, track_borrow, track_borrow_mut, track_borrow_mut_with_id,
109    track_borrow_with_id, track_cell_get, track_cell_new, track_cell_set, track_const_eval,
110    track_drop, track_drop_batch, track_drop_with_id, track_ffi_call, track_move,
111    track_move_with_id, track_new, track_new_with_id, track_raw_ptr, track_raw_ptr_deref,
112    track_raw_ptr_mut, track_rc_clone, track_rc_clone_with_id, track_rc_new, track_rc_new_with_id,
113    track_refcell_borrow, track_refcell_borrow_mut, track_refcell_drop, track_refcell_new,
114    track_static_access, track_static_init, track_transmute, track_union_field_access,
115    track_unsafe_block_enter, track_unsafe_block_exit, track_unsafe_fn_call, TrackingSummary,
116};
117
118/// Convenience macro for RefCell borrow tracking with auto file:line capture
119#[macro_export]
120macro_rules! refcell_borrow {
121    ($name:expr, $id:expr, $guard:expr) => {
122        $crate::track_refcell_borrow($name, $id, concat!(file!(), ":", line!()), $guard)
123    };
124}
125
126/// Convenience macro for RefCell borrow_mut tracking with auto file:line capture
127#[macro_export]
128macro_rules! refcell_borrow_mut {
129    ($name:expr, $id:expr, $guard:expr) => {
130        $crate::track_refcell_borrow_mut($name, $id, concat!(file!(), ":", line!()), $guard)
131    };
132}
133
134/// Convenience macro for RefCell drop tracking with auto file:line capture
135#[macro_export]
136macro_rules! refcell_drop {
137    ($name:expr) => {
138        $crate::track_refcell_drop($name, concat!(file!(), ":", line!()))
139    };
140}
141
142/// Get the ownership graph built from current events
143pub fn get_graph() -> OwnershipGraph {
144    let events = get_events();
145    build_graph(&events)
146}
147
148/// Export current tracking data to JSON file
149pub fn export_json<P: AsRef<std::path::Path>>(path: P) -> Result<()> {
150    let events = get_events();
151    let graph = build_graph(&events);
152    let export = ExportData::new(graph, events);
153    export.to_file(path)
154}
155
156#[cfg(test)]
157mod integration_tests {
158    use super::*;
159    use crate::test_utils::TEST_LOCK;
160
161    #[test]
162    fn test_simple_tracking() {
163        let _lock = TEST_LOCK.lock();
164
165        reset();
166
167        let x = track_new("x", 5);
168        assert_eq!(x, 5);
169
170        let events = get_events();
171        assert_eq!(events.len(), 1);
172        assert!(events[0].is_new());
173    }
174
175    #[test]
176    fn test_borrow_tracking() {
177        let _lock = TEST_LOCK.lock();
178
179        reset();
180
181        let s = track_new("s", String::from("hello"));
182        let r = track_borrow("r", &s);
183
184        assert_eq!(r, "hello");
185
186        let events = get_events();
187        assert_eq!(events.len(), 2);
188        assert!(events[0].is_new());
189        assert!(events[1].is_borrow());
190    }
191
192    #[test]
193    fn test_multiple_variables() {
194        let _lock = TEST_LOCK.lock();
195
196        reset();
197
198        let x = track_new("x", 5);
199        let y = track_new("y", 10);
200        let z = x + y;
201
202        track_drop("y");
203        track_drop("x");
204
205        let events = get_events();
206        assert_eq!(events.len(), 4);
207
208        assert_eq!(z, 15);
209    }
210
211    #[test]
212    fn test_mutable_borrow() {
213        let _lock = TEST_LOCK.lock();
214
215        reset();
216
217        let mut x = track_new("x", vec![1, 2, 3]);
218        let r = track_borrow_mut("r", &mut x);
219        r.push(4);
220
221        assert_eq!(r.len(), 4);
222
223        let events = get_events();
224        assert_eq!(events.len(), 2);
225    }
226
227    #[test]
228    fn test_graph_building() {
229        let _lock = TEST_LOCK.lock();
230
231        reset();
232
233        let x = track_new("x", 5);
234        let _r = track_borrow("r", &x);
235        // Note: borrowers don't get track_drop calls in current implementation
236        track_drop("x");
237
238        let graph = get_graph();
239        // Only x is tracked as a variable
240        assert_eq!(graph.nodes.len(), 1);
241        // No edges because borrow wasn't ended with a drop
242        assert_eq!(graph.edges.len(), 0);
243
244        let stats = graph.stats();
245        assert_eq!(stats.total_variables, 1);
246    }
247
248    #[test]
249    fn test_export_json() {
250        let _lock = TEST_LOCK.lock();
251
252        reset();
253
254        let x = track_new("x", 5);
255        let _r = track_borrow("r", &x);
256        track_drop("x");
257
258        // Export to temporary file
259        let temp_path = std::env::temp_dir().join("borrowscope_test.json");
260        export_json(&temp_path).unwrap();
261
262        // Verify file exists and is valid JSON
263        let contents = std::fs::read_to_string(&temp_path).unwrap();
264        let parsed: serde_json::Value = serde_json::from_str(&contents).unwrap();
265
266        assert!(parsed["nodes"].is_array());
267        assert!(parsed["events"].is_array());
268        assert!(parsed["metadata"].is_object());
269
270        // Cleanup
271        std::fs::remove_file(&temp_path).ok();
272    }
273}