nice_assert_no_alloc/
lib.rs1#![doc = include_str!("../README.md")]
25
26use std::alloc::{GlobalAlloc, Layout, System};
27use std::cell::Cell;
28
29#[cfg(all(feature = "disable_release", feature = "warn_release"))]
31compile_error!("disable_release cannot be active at the same time with warn_release");
32
33#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] thread_local! {
35 static ALLOC_FORBID_COUNT: Cell<u32> = Cell::new(0);
36 static ALLOC_PERMIT_COUNT: Cell<u32> = Cell::new(0);
37
38 #[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))]
39 static ALLOC_VIOLATION_COUNT: Cell<u32> = Cell::new(0);
40}
41
42#[cfg(all(feature = "disable_release", not(debug_assertions)))] pub fn assert_no_alloc<T, F: FnOnce() -> T>(func: F) -> T {
44 func()
46}
47
48#[cfg(all(feature = "disable_release", not(debug_assertions)))] pub fn permit_alloc<T, F: FnOnce() -> T>(func: F) -> T {
50 func()
52}
53
54#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] pub fn assert_no_alloc<T, F: FnOnce() -> T>(func: F) -> T {
62 struct Guard;
65 impl Guard {
66 fn new() -> Guard {
67 ALLOC_FORBID_COUNT.with(|c| c.set(c.get() + 1));
68 Guard
69 }
70 }
71 impl Drop for Guard {
72 fn drop(&mut self) {
73 ALLOC_FORBID_COUNT.with(|c| c.set(c.get() - 1));
74 }
75 }
76
77 #[cfg(any(
78 all(feature = "warn_debug", debug_assertions),
79 all(feature = "warn_release", not(debug_assertions))
80 ))] let old_violation_count = violation_count();
82
83 let guard = Guard::new(); let ret = func();
85 std::mem::drop(guard); #[cfg(any(
88 all(feature = "warn_debug", debug_assertions),
89 all(feature = "warn_release", not(debug_assertions))
90 ))] if violation_count() > old_violation_count {
92 eprintln!("Tried to (de)allocate memory in a thread that forbids allocator calls!");
93 }
94
95 return ret;
96}
97
98#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] pub fn permit_alloc<T, F: FnOnce() -> T>(func: F) -> T {
102 struct Guard;
104 impl Guard {
105 fn new() -> Guard {
106 ALLOC_PERMIT_COUNT.with(|c| c.set(c.get() + 1));
107 Guard
108 }
109 }
110 impl Drop for Guard {
111 fn drop(&mut self) {
112 ALLOC_PERMIT_COUNT.with(|c| c.set(c.get() - 1));
113 }
114 }
115
116 let guard = Guard::new(); let ret = func();
118 std::mem::drop(guard); return ret;
121}
122
123#[cfg(any(
124 all(feature = "warn_debug", debug_assertions),
125 all(feature = "warn_release", not(debug_assertions))
126))] pub fn violation_count() -> u32 {
131 ALLOC_VIOLATION_COUNT.with(|c| c.get())
132}
133
134#[cfg(any(
135 all(feature = "warn_debug", debug_assertions),
136 all(feature = "warn_release", not(debug_assertions))
137))] pub fn reset_violation_count() {
142 ALLOC_VIOLATION_COUNT.with(|c| c.set(0));
143}
144
145#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] pub struct AllocDisabler;
157
158#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] impl AllocDisabler {
160 fn check(&self, #[allow(unused)] layout: Layout) {
161 let forbid_count = ALLOC_FORBID_COUNT.with(|f| f.get());
162 let permit_count = ALLOC_PERMIT_COUNT.with(|p| p.get());
163 if forbid_count > permit_count {
164 #[cfg(any(
165 all(feature = "warn_debug", debug_assertions),
166 all(feature = "warn_release", not(debug_assertions))
167 ))] ALLOC_VIOLATION_COUNT.with(|c| c.set(c.get() + 1));
169
170 #[cfg(any(
171 all(not(feature = "warn_debug"), debug_assertions),
172 all(not(feature = "warn_release"), not(debug_assertions))
173 ))] {
175 #[cfg(all(feature = "log", feature = "backtrace"))]
176 permit_alloc(|| {
177 log::error!(
178 "Memory allocation of {} bytes failed from:\n{:?}",
179 layout.size(),
180 backtrace::Backtrace::new()
181 )
182 });
183 #[cfg(all(feature = "log", not(feature = "backtrace")))]
184 permit_alloc(|| log::error!("Memory allocation of {} bytes failed", layout.size()));
185
186 #[cfg(all(not(feature = "log"), feature = "backtrace"))]
187 permit_alloc(|| {
188 eprintln!(
189 "Allocation failure from:\n{:?}",
190 backtrace::Backtrace::new()
191 )
192 });
193
194 std::alloc::handle_alloc_error(layout);
197 }
198 }
199 }
200}
201
202#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] unsafe impl GlobalAlloc for AllocDisabler {
204 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
205 self.check(layout);
206 System.alloc(layout)
207 }
208
209 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
210 self.check(layout);
211 System.dealloc(ptr, layout)
212 }
213}
214
215pub struct PermitDrop<T>(Option<T>);
233
234impl<T> PermitDrop<T> {
235 pub fn new(t: T) -> PermitDrop<T> {
236 permit_alloc(|| PermitDrop(Some(t)))
237 }
238}
239
240impl<T> std::ops::Deref for PermitDrop<T> {
241 type Target = T;
242 fn deref(&self) -> &T {
243 self.0.as_ref().unwrap()
244 }
245}
246
247impl<T> std::ops::DerefMut for PermitDrop<T> {
248 fn deref_mut(&mut self) -> &mut T {
249 self.0.as_mut().unwrap()
250 }
251}
252
253impl<I: Iterator> Iterator for PermitDrop<I> {
254 type Item = I::Item;
255 fn next(&mut self) -> Option<Self::Item> {
256 (**self).next()
257 }
258}
259
260impl<T> Drop for PermitDrop<T> {
261 fn drop(&mut self) {
262 let mut tmp = None;
263 std::mem::swap(&mut tmp, &mut self.0);
264 permit_alloc(|| {
265 std::mem::drop(tmp);
266 });
267 }
268}