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, smart pointer operations,
7//! concurrency primitives, and unsafe code as they happen at runtime, generating
8//! structured event data for analysis and visualization.
9//!
10//! ## Quick Start
11//!
12//! ```rust
13//! use borrowscope_runtime::*;
14//!
15//! // Clear previous tracking data
16//! reset();
17//!
18//! // Track variable creation
19//! let data = track_new("data", vec![1, 2, 3]);
20//!
21//! // Track borrowing
22//! let r = track_borrow("r", &data);
23//! println!("{:?}", r);
24//!
25//! // Track drops
26//! track_drop("r");
27//! track_drop("data");
28//!
29//! // Export events as JSON
30//! let events = get_events();
31//! println!("{}", serde_json::to_string_pretty(&events).unwrap());
32//! ```
33//!
34//! ## Feature Flags
35//!
36//! - `track` - Enables runtime tracking. Without this feature, all tracking
37//!   functions compile to no-ops with zero overhead.
38//!
39//! ```toml
40//! [dependencies]
41//! borrowscope-runtime = { version = "0.1", features = ["track"] }
42//! ```
43//!
44//! ## Tracking Functions by Category
45//!
46//! ### Basic Ownership
47//!
48//! Core ownership tracking for variables, borrows, moves, and drops.
49//!
50//! | Function | Description |
51//! |----------|-------------|
52//! | [`track_new`] | Track variable creation |
53//! | [`track_new_with_id`] | Track creation with explicit ID and location |
54//! | [`track_borrow`] | Track immutable borrow (`&T`) |
55//! | [`track_borrow_with_id`] | Track immutable borrow with explicit IDs |
56//! | [`track_borrow_mut`] | Track mutable borrow (`&mut T`) |
57//! | [`track_borrow_mut_with_id`] | Track mutable borrow with explicit IDs |
58//! | [`track_move`] | Track ownership transfer |
59//! | [`track_move_with_id`] | Track move with explicit IDs |
60//! | [`track_drop`] | Track variable going out of scope |
61//! | [`track_drop_with_id`] | Track drop with explicit ID |
62//! | [`track_drop_batch`] | Track multiple drops efficiently |
63//!
64//! ### Smart Pointers
65//!
66//! Track reference-counted and heap-allocated smart pointers.
67//!
68//! | Function | Description |
69//! |----------|-------------|
70//! | [`track_rc_new`] | Track `Rc::new` |
71//! | [`track_rc_clone`] | Track `Rc::clone` |
72//! | [`track_arc_new`] | Track `Arc::new` |
73//! | [`track_arc_clone`] | Track `Arc::clone` |
74//! | [`track_weak_new`] | Track `Rc::downgrade` |
75//! | [`track_weak_new_sync`] | Track `Arc::downgrade` |
76//! | [`track_weak_clone`] | Track `Weak::clone` (Rc) |
77//! | [`track_weak_clone_sync`] | Track `Weak::clone` (Arc) |
78//! | [`track_weak_upgrade`] | Track `Weak::upgrade` (Rc) |
79//! | [`track_weak_upgrade_sync`] | Track `Weak::upgrade` (Arc) |
80//! | [`track_box_new`] | Track `Box::new` |
81//! | [`track_box_into_raw`] | Track `Box::into_raw` |
82//! | [`track_box_from_raw`] | Track `Box::from_raw` |
83//! | [`track_pin_new`] | Track `Pin::new` |
84//! | [`track_pin_into_inner`] | Track `Pin::into_inner` |
85//! | [`track_cow_borrowed`] | Track `Cow::Borrowed` |
86//! | [`track_cow_owned`] | Track `Cow::Owned` |
87//! | [`track_cow_to_mut`] | Track `Cow::to_mut` |
88//!
89//! ### Interior Mutability
90//!
91//! Track runtime borrow checking and cell types.
92//!
93//! | Function | Description |
94//! |----------|-------------|
95//! | [`track_refcell_new`] | Track `RefCell::new` |
96//! | [`track_refcell_borrow`] | Track `RefCell::borrow` |
97//! | [`track_refcell_borrow_mut`] | Track `RefCell::borrow_mut` |
98//! | [`track_refcell_drop`] | Track `Ref`/`RefMut` guard drop |
99//! | [`track_cell_new`] | Track `Cell::new` |
100//! | [`track_cell_get`] | Track `Cell::get` |
101//! | [`track_cell_set`] | Track `Cell::set` |
102//! | [`track_once_cell_new`] | Track `OnceCell::new` |
103//! | [`track_once_lock_new`] | Track `OnceLock::new` |
104//! | [`track_once_cell_set`] | Track `OnceCell::set` |
105//! | [`track_once_cell_get`] | Track `OnceCell::get` |
106//! | [`track_once_cell_get_or_init`] | Track `OnceCell::get_or_init` |
107//! | [`track_maybe_uninit_uninit`] | Track `MaybeUninit::uninit` |
108//! | [`track_maybe_uninit_new`] | Track `MaybeUninit::new` |
109//! | [`track_maybe_uninit_write`] | Track `MaybeUninit::write` |
110//! | [`track_maybe_uninit_assume_init`] | Track `MaybeUninit::assume_init` |
111//! | [`track_maybe_uninit_assume_init_read`] | Track `MaybeUninit::assume_init_read` |
112//! | [`track_maybe_uninit_assume_init_drop`] | Track `MaybeUninit::assume_init_drop` |
113//!
114//! ### Unsafe Code
115//!
116//! Track unsafe operations, raw pointers, and FFI.
117//!
118//! | Function | Description |
119//! |----------|-------------|
120//! | [`track_raw_ptr`] | Track `*const T` creation |
121//! | [`track_raw_ptr_mut`] | Track `*mut T` creation |
122//! | [`track_raw_ptr_deref`] | Track raw pointer dereference |
123//! | [`track_unsafe_block_enter`] | Track entering `unsafe` block |
124//! | [`track_unsafe_block_exit`] | Track exiting `unsafe` block |
125//! | [`track_unsafe_fn_call`] | Track unsafe function call |
126//! | [`track_ffi_call`] | Track FFI function call |
127//! | [`track_transmute`] | Track `std::mem::transmute` |
128//! | [`track_union_field_access`] | Track union field access |
129//!
130//! ### Concurrency
131//!
132//! Track threads, channels, and synchronization primitives.
133//!
134//! | Function | Description |
135//! |----------|-------------|
136//! | [`track_thread_spawn`] | Track `thread::spawn` |
137//! | [`track_thread_join`] | Track `JoinHandle::join` |
138//! | [`track_channel`] | Track `mpsc::channel` creation |
139//! | [`track_channel_send`] | Track `Sender::send` |
140//! | [`track_channel_recv`] | Track `Receiver::recv` |
141//! | [`track_channel_try_recv`] | Track `Receiver::try_recv` |
142//! | [`track_lock`] | Track lock acquisition |
143//! | [`track_lock_guard_acquire`] | Track lock guard creation |
144//! | [`track_lock_guard_drop`] | Track lock guard drop |
145//!
146//! ### Async/Await
147//!
148//! Track async blocks and await points.
149//!
150//! | Function | Description |
151//! |----------|-------------|
152//! | [`track_async_block_enter`] | Track entering async block |
153//! | [`track_async_block_exit`] | Track exiting async block |
154//! | [`track_await_start`] | Track await expression start |
155//! | [`track_await_end`] | Track await expression completion |
156//!
157//! ### Control Flow
158//!
159//! Track loops, matches, branches, and function boundaries.
160//!
161//! | Function | Description |
162//! |----------|-------------|
163//! | [`track_loop_enter`] | Track loop entry |
164//! | [`track_loop_iteration`] | Track loop iteration |
165//! | [`track_loop_exit`] | Track loop exit |
166//! | [`track_match_enter`] | Track match expression entry |
167//! | [`track_match_arm`] | Track match arm taken |
168//! | [`track_match_exit`] | Track match expression exit |
169//! | [`track_branch`] | Track if/else branch |
170//! | [`track_return`] | Track return statement |
171//! | [`track_try`] | Track `?` operator |
172//! | [`track_break`] | Track break statement |
173//! | [`track_continue`] | Track continue statement |
174//! | [`track_fn_enter`] | Track function entry |
175//! | [`track_fn_exit`] | Track function exit |
176//! | [`track_region_enter`] | Track scope/region entry |
177//! | [`track_region_exit`] | Track scope/region exit |
178//!
179//! ### Expressions
180//!
181//! Track various expression types and operations.
182//!
183//! | Function | Description |
184//! |----------|-------------|
185//! | [`track_index_access`] | Track array/slice indexing |
186//! | [`track_field_access`] | Track struct field access |
187//! | [`track_call`] | Track function/method call |
188//! | [`track_unwrap`] | Track `unwrap`/`expect` calls |
189//! | [`track_clone`] | Track `clone` calls |
190//! | [`track_deref`] | Track dereference operations |
191//! | [`track_closure_create`] | Track closure creation |
192//! | [`track_closure_capture`] | Track closure variable capture |
193//! | [`track_struct_create`] | Track struct instantiation |
194//! | [`track_tuple_create`] | Track tuple creation |
195//! | [`track_array_create`] | Track array creation |
196//! | [`track_let_else`] | Track let-else patterns |
197//! | [`track_range`] | Track range expressions |
198//! | [`track_binary_op`] | Track binary operations |
199//! | [`track_type_cast`] | Track type casts (`as`) |
200//!
201//! ### Static/Const
202//!
203//! Track static variables and const evaluation.
204//!
205//! | Function | Description |
206//! |----------|-------------|
207//! | [`track_static_init`] | Track static variable initialization |
208//! | [`track_static_access`] | Track static variable access |
209//! | [`track_const_eval`] | Track const evaluation |
210//!
211//! ### Sampling
212//!
213//! Probabilistic tracking for reduced overhead.
214//!
215//! | Function | Description |
216//! |----------|-------------|
217//! | [`should_sample`] | Check if call should be sampled |
218//! | [`track_new_sampled`] | Track creation with sampling |
219//! | [`track_new_with_id_sampled`] | Track creation with ID and sampling |
220//! | [`track_borrow_sampled`] | Track borrow with sampling |
221//! | [`track_borrow_mut_sampled`] | Track mutable borrow with sampling |
222//! | [`track_drop_sampled`] | Track drop with sampling |
223//! | [`track_move_sampled`] | Track move with sampling |
224//!
225//! ### Query Functions
226//!
227//! Filter and summarize tracked events.
228//!
229//! | Function | Description |
230//! |----------|-------------|
231//! | [`get_events`] | Get all recorded events |
232//! | [`get_events_filtered`] | Get events matching predicate |
233//! | [`get_new_events`] | Get all `New` events |
234//! | [`get_borrow_events`] | Get all `Borrow` events |
235//! | [`get_drop_events`] | Get all `Drop` events |
236//! | [`get_move_events`] | Get all `Move` events |
237//! | [`get_events_for_var`] | Get events for specific variable |
238//! | [`get_event_counts`] | Get (new, borrow, move, drop) counts |
239//! | [`get_summary`] | Get [`TrackingSummary`] statistics |
240//! | [`print_summary`] | Print summary to stdout |
241//! | [`reset`] | Clear all recorded events |
242//!
243//! ## RAII Guards
244//!
245//! For automatic drop tracking, use the guard variants:
246//!
247//! ```rust
248//! use borrowscope_runtime::*;
249//!
250//! reset();
251//! {
252//!     let data = track_new_guard("data", vec![1, 2, 3]);
253//!     println!("{:?}", *data);
254//!     // track_drop("data") called automatically when data goes out of scope
255//! }
256//!
257//! let events = get_events();
258//! assert!(events.last().unwrap().is_drop());
259//! ```
260//!
261//! ## Performance
262//!
263//! - With `track` feature: ~75-80ns per tracking call
264//! - Without `track` feature: zero overhead (compiled away)
265
266mod error;
267mod event;
268mod export;
269mod graph;
270mod guard;
271mod lifetime;
272mod tracker;
273
274#[cfg(test)]
275mod test_utils;
276
277pub use error::{BorrowScopeResult, Error};
278pub use event::Event;
279pub use export::{ExportData, ExportEdge, ExportMetadata};
280pub use graph::{build_graph, GraphStats, OwnershipGraph, Relationship, Variable};
281pub use guard::{
282    track_borrow_guard, track_borrow_mut_guard, track_new_guard, BorrowGuard, BorrowMutGuard,
283    TrackGuard,
284};
285pub use lifetime::{ElisionRule, LifetimeRelation, Timeline};
286pub use tracker::{
287    __track_new_with_id_helper, get_borrow_events, get_drop_events, get_event_counts, get_events,
288    get_events_filtered, get_events_for_var, get_move_events, get_new_events, get_summary,
289    print_summary, reset, should_sample, track_arc_clone, track_arc_clone_with_id, track_arc_new,
290    track_arc_new_with_id, track_array_create, track_async_block_enter, track_async_block_exit,
291    track_await_end, track_await_start, track_binary_op, track_borrow, track_borrow_mut,
292    track_borrow_mut_sampled, track_borrow_mut_with_id, track_borrow_sampled, track_borrow_with_id,
293    track_box_from_raw, track_box_into_raw, track_box_new, track_branch, track_break, track_call,
294    track_cell_get, track_cell_new, track_cell_set, track_channel, track_channel_recv,
295    track_channel_send, track_channel_try_recv, track_clone, track_closure_capture,
296    track_closure_create, track_const_eval, track_continue, track_cow_borrowed, track_cow_owned,
297    track_cow_to_mut, track_deref, track_drop, track_drop_batch, track_drop_sampled,
298    track_drop_with_id, track_ffi_call, track_field_access, track_fn_enter, track_fn_exit,
299    track_index_access, track_let_else, track_lock, track_lock_guard_acquire, track_lock_guard_drop,
300    track_loop_enter, track_loop_exit, track_loop_iteration, track_match_arm, track_match_enter,
301    track_match_exit, track_maybe_uninit_assume_init, track_maybe_uninit_assume_init_drop,
302    track_maybe_uninit_assume_init_read, track_maybe_uninit_new, track_maybe_uninit_uninit,
303    track_maybe_uninit_write, track_move, track_move_sampled, track_move_with_id, track_new,
304    track_new_sampled, track_new_with_id, track_new_with_id_sampled, track_once_cell_get,
305    track_once_cell_get_or_init, track_once_cell_new, track_once_cell_set, track_once_lock_new,
306    track_pin_into_inner, track_pin_new, track_range, track_raw_ptr, track_raw_ptr_deref,
307    track_raw_ptr_mut, track_rc_clone, track_rc_clone_with_id, track_rc_new, track_rc_new_with_id,
308    track_refcell_borrow, track_refcell_borrow_mut, track_refcell_drop, track_refcell_new,
309    track_region_enter, track_region_exit, track_return, track_static_access, track_static_init,
310    track_struct_create, track_thread_join, track_thread_spawn, track_transmute, track_try,
311    track_tuple_create, track_type_cast, track_union_field_access, track_unsafe_block_enter,
312    track_unsafe_block_exit, track_unsafe_fn_call, track_unwrap, track_weak_clone,
313    track_weak_clone_sync, track_weak_new, track_weak_new_sync, track_weak_upgrade,
314    track_weak_upgrade_sync, TrackingSummary,
315};
316
317/// Convenience macro for RefCell borrow tracking with auto file:line capture
318#[macro_export]
319macro_rules! refcell_borrow {
320    ($name:expr, $id:expr, $guard:expr) => {
321        $crate::track_refcell_borrow($name, $id, concat!(file!(), ":", line!()), $guard)
322    };
323}
324
325/// Convenience macro for RefCell borrow_mut tracking with auto file:line capture
326#[macro_export]
327macro_rules! refcell_borrow_mut {
328    ($name:expr, $id:expr, $guard:expr) => {
329        $crate::track_refcell_borrow_mut($name, $id, concat!(file!(), ":", line!()), $guard)
330    };
331}
332
333/// Convenience macro for RefCell drop tracking with auto file:line capture
334#[macro_export]
335macro_rules! refcell_drop {
336    ($name:expr) => {
337        $crate::track_refcell_drop($name, concat!(file!(), ":", line!()))
338    };
339}
340
341/// Get the ownership graph built from current events
342pub fn get_graph() -> OwnershipGraph {
343    let events = get_events();
344    build_graph(&events)
345}
346
347/// Export current tracking data to JSON file
348pub fn export_json<P: AsRef<std::path::Path>>(path: P) -> BorrowScopeResult<()> {
349    let events = get_events();
350    let graph = build_graph(&events);
351    let export = ExportData::new(graph, events);
352    export.to_file(path)
353}
354
355#[cfg(test)]
356mod integration_tests {
357    use super::*;
358    use crate::test_utils::TEST_LOCK;
359
360    #[test]
361    fn test_simple_tracking() {
362        let _lock = TEST_LOCK.lock();
363
364        reset();
365
366        let x = track_new("x", 5);
367        assert_eq!(x, 5);
368
369        let events = get_events();
370        assert_eq!(events.len(), 1);
371        assert!(events[0].is_new());
372    }
373
374    #[test]
375    fn test_borrow_tracking() {
376        let _lock = TEST_LOCK.lock();
377
378        reset();
379
380        let s = track_new("s", String::from("hello"));
381        let r = track_borrow("r", &s);
382
383        assert_eq!(r, "hello");
384
385        let events = get_events();
386        assert_eq!(events.len(), 2);
387        assert!(events[0].is_new());
388        assert!(events[1].is_borrow());
389    }
390
391    #[test]
392    fn test_multiple_variables() {
393        let _lock = TEST_LOCK.lock();
394
395        reset();
396
397        let x = track_new("x", 5);
398        let y = track_new("y", 10);
399        let z = x + y;
400
401        track_drop("y");
402        track_drop("x");
403
404        let events = get_events();
405        assert_eq!(events.len(), 4);
406
407        assert_eq!(z, 15);
408    }
409
410    #[test]
411    fn test_mutable_borrow() {
412        let _lock = TEST_LOCK.lock();
413
414        reset();
415
416        let mut x = track_new("x", vec![1, 2, 3]);
417        let r = track_borrow_mut("r", &mut x);
418        r.push(4);
419
420        assert_eq!(r.len(), 4);
421
422        let events = get_events();
423        assert_eq!(events.len(), 2);
424    }
425
426    #[test]
427    fn test_graph_building() {
428        let _lock = TEST_LOCK.lock();
429
430        reset();
431
432        let x = track_new("x", 5);
433        let _r = track_borrow("r", &x);
434        // Note: borrowers don't get track_drop calls in current implementation
435        track_drop("x");
436
437        let graph = get_graph();
438        // Only x is tracked as a variable
439        assert_eq!(graph.nodes.len(), 1);
440        // No edges because borrow wasn't ended with a drop
441        assert_eq!(graph.edges.len(), 0);
442
443        let stats = graph.stats();
444        assert_eq!(stats.total_variables, 1);
445    }
446
447    #[test]
448    fn test_export_json() {
449        let _lock = TEST_LOCK.lock();
450
451        reset();
452
453        let x = track_new("x", 5);
454        let _r = track_borrow("r", &x);
455        track_drop("x");
456
457        // Export to temporary file
458        let temp_path = std::env::temp_dir().join("borrowscope_test.json");
459        export_json(&temp_path).unwrap();
460
461        // Verify file exists and is valid JSON
462        let contents = std::fs::read_to_string(&temp_path).unwrap();
463        let parsed: serde_json::Value = serde_json::from_str(&contents).unwrap();
464
465        assert!(parsed["nodes"].is_array());
466        assert!(parsed["events"].is_array());
467        assert!(parsed["metadata"].is_object());
468
469        // Cleanup
470        std::fs::remove_file(&temp_path).ok();
471    }
472}