tree_sitter_cli/fuzz/
allocations.rs1use std::{
2 collections::HashMap,
3 os::raw::c_void,
4 sync::{
5 atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
6 Mutex,
7 },
8};
9
10#[ctor::ctor]
11unsafe fn initialize_allocation_recording() {
12 tree_sitter::set_allocator(
13 Some(ts_record_malloc),
14 Some(ts_record_calloc),
15 Some(ts_record_realloc),
16 Some(ts_record_free),
17 );
18}
19
20#[derive(Debug, PartialEq, Eq, Hash)]
21struct Allocation(*const c_void);
22unsafe impl Send for Allocation {}
23unsafe impl Sync for Allocation {}
24
25#[derive(Default)]
26struct AllocationRecorder {
27 enabled: AtomicBool,
28 allocation_count: AtomicUsize,
29 outstanding_allocations: Mutex<HashMap<Allocation, usize>>,
30}
31
32thread_local! {
33 static RECORDER: AllocationRecorder = AllocationRecorder::default();
34}
35
36extern "C" {
37 fn malloc(size: usize) -> *mut c_void;
38 fn calloc(count: usize, size: usize) -> *mut c_void;
39 fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
40 fn free(ptr: *mut c_void);
41}
42
43pub fn record<T>(f: impl FnOnce() -> T) -> Result<T, String> {
44 RECORDER.with(|recorder| {
45 recorder.enabled.store(true, SeqCst);
46 recorder.allocation_count.store(0, SeqCst);
47 recorder.outstanding_allocations.lock().unwrap().clear();
48 });
49
50 let value = f();
51
52 let outstanding_allocation_indices = RECORDER.with(|recorder| {
53 recorder.enabled.store(false, SeqCst);
54 recorder.allocation_count.store(0, SeqCst);
55 recorder
56 .outstanding_allocations
57 .lock()
58 .unwrap()
59 .drain()
60 .map(|e| e.1)
61 .collect::<Vec<_>>()
62 });
63 if !outstanding_allocation_indices.is_empty() {
64 return Err(format!(
65 "Leaked allocation indices: {outstanding_allocation_indices:?}",
66 ));
67 }
68 Ok(value)
69}
70
71fn record_alloc(ptr: *mut c_void) {
72 RECORDER.with(|recorder| {
73 if recorder.enabled.load(SeqCst) {
74 let count = recorder.allocation_count.fetch_add(1, SeqCst);
75 recorder
76 .outstanding_allocations
77 .lock()
78 .unwrap()
79 .insert(Allocation(ptr), count);
80 }
81 });
82}
83
84fn record_dealloc(ptr: *mut c_void) {
85 RECORDER.with(|recorder| {
86 if recorder.enabled.load(SeqCst) {
87 recorder
88 .outstanding_allocations
89 .lock()
90 .unwrap()
91 .remove(&Allocation(ptr));
92 }
93 });
94}
95
96unsafe extern "C" fn ts_record_malloc(size: usize) -> *mut c_void {
97 let result = malloc(size);
98 record_alloc(result);
99 result
100}
101
102unsafe extern "C" fn ts_record_calloc(count: usize, size: usize) -> *mut c_void {
103 let result = calloc(count, size);
104 record_alloc(result);
105 result
106}
107
108unsafe extern "C" fn ts_record_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
109 let result = realloc(ptr, size);
110 if ptr.is_null() {
111 record_alloc(result);
112 } else if ptr != result {
113 record_dealloc(ptr);
114 record_alloc(result);
115 }
116 result
117}
118
119unsafe extern "C" fn ts_record_free(ptr: *mut c_void) {
120 record_dealloc(ptr);
121 free(ptr);
122}