smartalloc/
lib.rs

1//! [![no std](https://img.shields.io/badge/no-std-red)](https://img.shields.io/badge/no-std-red)
2//! [![crates.io](https://img.shields.io/crates/v/smartalloc.svg)](https://crates.io/crates/smartalloc)
3//! [![docs.rs](https://docs.rs/smartalloc/badge.svg)](https://docs.rs/smartalloc)
4//! [![GitHub](https://img.shields.io/crates/l/smartalloc)](https://github.com/ehsanmok/smartalloc-rs)
5//!
6//! <br>
7//!
8//! This crate provides a `no_std` idiomatic Rust binding to [smartalloc](https://www.fourmilab.ch/smartall/) used for
9//! **detecting orphaned buffer allocation** which is a type of heap memory leak that the program has lost all access to it.
10//! The primary usecase is as a *debugging* tool when writing **unsafe** code where normal Rust static checks are not available.
11//! It is best used along side [SANs](https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html) where SANs
12//! alone are either unable to detect or their outputs are cumbersome to work through.
13//! To get the best experience, `RUSTFLAGS=-Zsanitizer=leak` is used and is included in `.cargo/config.toml`.
14//!
15//! <br>
16//!
17//! ## Usage
18//!
19//! ```ini
20//! [dev-dependencies]
21//! smartalloc = "0.2"
22//! ```
23//!
24//! In fact, with `#![cfg(debug_assertions)]` the crate does **not** compile in the `--release` mode so preventing from any accidental usage.
25//! The crate **requires nightly** Rust toolchain (MSRV 1.65).
26//!
27//! <br>
28//!
29//! ## Example
30//!
31//! During debugging, configure the `SmartAlloc` as the global allocator. Then include `sm_dump(true)` at the end of an unsafe code block.
32//! Here is the [examples/orphan.rs](https://github.com/ehsanmok/smartalloc-rs/blob/main/examples/orphan.rs)
33//!
34//! ```no_run
35//! use core::alloc::{GlobalAlloc, Layout};
36//!
37//! use smartalloc::{sm_dump, SmartAlloc};
38//!
39//! #[global_allocator]
40//! static GLOBAL: SmartAlloc = SmartAlloc;
41//!
42//! fn main() {
43//!     unsafe {
44//!         let alloc = SmartAlloc;
45//!         let layout = Layout::from_size_align(8, 8).unwrap();
46//!         alloc.alloc(layout); // orphaned memory leak as it's pointer is lost
47//!                              // and there's no alloc.dealloc(ptr, layout)
48//!         sm_dump(true);
49//!     }
50//! }
51//! ```
52//!
53//! which outputs
54//!
55//! ```txt
56//! Orphaned buffer:       8 bytes allocated at line 12 of examples/orphan.rs
57//! ```
58//!
59//! *Note* that the detector also throws
60//!
61//! ```txt
62//! Orphaned buffer:       5 bytes allocated at line 5 of examples/orphan.rs
63//! Orphaned buffer:      48 bytes allocated at line 5 of examples/orphan.rs
64//! ```
65//!
66//! which refers to the `#[global_allocator]` itself and can be ignored.
67//!
68//! <br>
69//!
70//! ## Features
71//!
72//! The detector can be turned off using `sm_static(true)` and turned back on `sm_static(false)` to wrap cases where allocation
73//! is done through std or safe cases such as [examples/native.rs](https://github.com/ehsanmok/smartalloc-rs/blob/main/examples/native.rs).
74//! For more details, checkout the original [docs](https://www.fourmilab.ch/smartall/).
75//!
76//! ## Aren't SANs alone supposed to detect such errors?
77//!
78//! Neither of the `leak/address/memory` [sanitizers](https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html) are sufficient and can detect such errors *easily*.
79//! In fact, running
80//!
81//! ```txt
82//! RUSTFLAGS="-Zsanitizer=leak" cargo +nightly run --example undetected
83//! // OR
84//! RUSTFLAGS="-Zsanitizer=address" cargo +nightly run --example undetected
85//! ```
86//!
87//! for [examples/undetected.rs](https://github.com/ehsanmok/smartalloc-rs/blob/main/examples/undetected.rs) which is
88//!
89//! ```no_run
90//! unsafe {
91//!     let alloc = SmartAlloc;
92//!     let layout = Layout::from_size_align(8, 8).unwrap();
93//!     alloc.alloc(layout);
94//! }
95//! ```
96//!
97//! with no `sm_dump(true)` at the end, does not show anything, mainly because we specified
98//!
99//! ```ini
100//! [profile.dev]
101//! opt-level = 0
102//! ```
103//!
104//! for the SmartAlloc to work with introspection as opposed to what has been advised to include (at least `opt-level=1`)
105//! [here](https://github.com/japaric/rust-san#unrealiable-leaksanitizer)
106//! to cirvumvent such a limitation but when is done the context gets destroyed. Also
107//!
108//! ```txt
109//! RUSTFLAGS="-Zsanitizer=memory -Zsanitizer-memory-track-origins" cargo +nightly run --example undetected
110//! ```
111//!
112//! cannot compile and it throws unhelpful messages
113//!
114//! ```txt
115//! error: failed to run custom build command for `libc v0.2.132`
116//!
117//! Caused by:
118//!   process didn't exit successfully: `/home/workspace/smartalloc-rs/target/debug/build/libc-02d4e594eff5723f/build-script-build` (exit status: 1)
119//!   --- stdout
120//!   cargo:rerun-if-changed=build.rs
121//!
122//!   --- stderr
123//!   ==186416==WARNING: MemorySanitizer: use-of-uninitialized-value
124//!     #0 0x56367729226c  (/home/workspace/smartalloc-rs/target/debug/build/libc-02d4e594eff5723f/build-script-build+0x7a26c) (BuildId: ff090caba1904387acf3f0fecb58801c6fa5caed)
125//!     #1 0x56367728e95d  (/home/workspace/smartalloc-rs/target/debug/build/libc-02d4e594eff5723f/build-script-build+0x7695d) (BuildId: ff090caba1904387acf3f0fecb58801c6fa5caed)
126//!     ...
127//!     Uninitialized value was created by an allocation of '_2' in the stack frame of function '_ZN18build_script_build19rustc_minor_nightly17hfbf53e202478a57bE'
128//!       #0 0x563677291e70  (/home/workspace/smartalloc-rs/target/debug/build/libc-02d4e594eff5723f/build-script-build+0x79e70) (BuildId: ff090caba1904387acf3f0fecb58801c6fa5caed)
129//!
130//!     SUMMARY: MemorySanitizer: use-of-uninitialized-value (/home/workspace/smartalloc-rs/target/debug/build/libc-02d4e594eff5723f/build-script-build+0x7a26c) (BuildId: ff090caba1904387acf3f0fecb58801c6fa5caed)
131//!     Exiting
132//! ```
133//!
134//! so it needs more work!
135//!
136//! <br>
137//!
138//! ## Known issue
139//!
140//! [smartalloc-sys/csrc/smartall.c](https://github.com/ehsanmok/smartalloc-rs/blob/main/smartalloc-sys/csrc/smartall.c)
141//! writes into the passed filename pointer tracked by `#[track_caller]` (which is immutable)
142//! which is an UB that could result into displaying more garbage after the filename in its report using this binding.
143
144#![no_std]
145#![crate_type = "lib"]
146#![cfg(debug_assertions)]
147
148extern crate smartalloc_sys as ffi;
149
150use ffi::{c_char, c_int, c_void};
151
152use core::alloc::{GlobalAlloc, Layout};
153use core::panic::Location;
154
155/// Prints orphaned buffers when enabled `true` with
156/// the number of allocated bytes and the line location in source code
157pub fn sm_dump(enable: bool) {
158    unsafe { ffi::sm_dump(enable.into()) }
159}
160
161/// Orphaned buffer detection can be disabled  (for  such
162/// items  as buffers allocated during initialisation) by
163/// calling `sm_static(true)`. Normal orphaned buffer
164/// detection  can be re-enabled with `sm_static(false)`. Note
165/// that all the other safeguards still apply to  buffers
166/// allocated when `sm_static(true)` mode is in effect.
167pub fn sm_static(enable: bool) {
168    unsafe { ffi::sm_static(enable.into()) }
169}
170
171/// SmartAlloc allocator which needs to be use with `#[global_allocator]`
172pub struct SmartAlloc;
173
174unsafe impl GlobalAlloc for SmartAlloc {
175    #[track_caller]
176    #[inline]
177    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
178        let caller_loc = Location::caller();
179        let fname = caller_loc.file();
180        let lineno = caller_loc.line();
181        ffi::sm_malloc(
182            // this is an unfortunate UB making the output contain some garbage
183            // and seems no other way to make it safe as
184            // `smartall.c` writes into the passed ptr and `fname` is behind a shared pointer
185            // and also neither of `core::cell::Cell` or `alloc::string::String.as_mut_ptr`
186            // can guarentee UTF-8 validity concernes!
187            fname.as_ptr() as *const _ as *mut c_char,
188            lineno as c_int,
189            layout.size(),
190        ) as *mut u8
191    }
192
193    #[inline]
194    unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
195        ffi::sm_free(ptr as *mut c_void);
196    }
197
198    #[track_caller]
199    #[inline]
200    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
201        let caller_loc = Location::caller();
202        let fname = caller_loc.file();
203        let lineno = caller_loc.line();
204        ffi::sm_calloc(
205            // this is an unfortunate UB making the output contain some garbage
206            // and seems no other way to make it safe as
207            // `smartall.c` writes into the passed ptr and `fname` is behind a shared pointer
208            // and also neither of `core::cell::Cell` or `alloc::string::String.as_mut_ptr`
209            // can guarentee UTF-8 validity concernes!
210            fname.as_ptr() as *const _ as *mut c_char,
211            lineno as c_int,
212            1,
213            layout.size(),
214        ) as *mut u8
215    }
216
217    #[track_caller]
218    #[inline]
219    unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
220        let caller_loc = Location::caller();
221        let fname = caller_loc.file();
222        let lineno = caller_loc.line();
223        ffi::sm_realloc(
224            // this is an unfortunate UB making the output contain some garbage
225            // and seems no other way to make it safe as
226            // `smartall.c` writes into the passed ptr and `fname` is behind a shared pointer
227            // and also neither of `core::cell::Cell` or `alloc::string::String.as_mut_ptr`
228            // can guarentee UTF-8 validity concernes!
229            fname.as_ptr() as *const _ as *mut c_char,
230            lineno as c_int,
231            ptr as *mut c_void,
232            new_size,
233        ) as *mut u8
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn free_alloc() {
243        unsafe {
244            let layout = Layout::from_size_align(8, 8).unwrap();
245            let alloc = SmartAlloc;
246            let ptr = alloc.alloc(layout);
247            alloc.dealloc(ptr, layout);
248        }
249        sm_dump(true);
250    }
251
252    #[test]
253    fn free_big_alloc() {
254        unsafe {
255            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
256            let alloc = SmartAlloc;
257            let ptr = alloc.alloc(layout);
258            alloc.dealloc(ptr, layout);
259            sm_dump(true)
260        }
261    }
262
263    #[test]
264    fn free_zero_alloc() {
265        unsafe {
266            let layout = Layout::from_size_align(8, 8).unwrap();
267            let alloc = SmartAlloc;
268            let ptr = alloc.alloc_zeroed(layout);
269            alloc.dealloc(ptr, layout);
270            sm_dump(true)
271        }
272    }
273
274    #[test]
275    fn free_zero_big_alloc() {
276        unsafe {
277            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
278            let alloc = SmartAlloc;
279            let ptr = alloc.alloc_zeroed(layout);
280            alloc.dealloc(ptr, layout);
281            sm_dump(true)
282        }
283    }
284
285    #[test]
286    fn free_realloc() {
287        unsafe {
288            let layout = Layout::from_size_align(8, 8).unwrap();
289            let alloc = SmartAlloc;
290            let ptr = alloc.alloc(layout);
291            let ptr = alloc.realloc(ptr, layout, 16);
292            alloc.dealloc(ptr, layout);
293            sm_dump(true)
294        }
295    }
296
297    #[test]
298    fn free_big_realloc() {
299        unsafe {
300            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
301            let alloc = SmartAlloc;
302            let ptr = alloc.alloc(layout);
303            let ptr = alloc.realloc(ptr, layout, 2 << 20);
304            alloc.dealloc(ptr, layout);
305            sm_dump(true)
306        }
307    }
308}