Skip to main content

assert_no_alloc_patched/
lib.rs

1/* assert_no_alloc -- A custom Rust allocator allowing to temporarily disable
2 * memory (de)allocations for a thread.
3 *
4 * Copyright (c) 2020 Florian Jung <flo@windfis.ch>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer.
11 *
12 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
13 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
15 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
16 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
17 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
18 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
19 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
20 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
21 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 */
23
24#![doc = include_str!("../README.md")]
25
26use std::alloc::{System,GlobalAlloc,Layout};
27use std::cell::Cell;
28
29// check for mutually exclusive features.
30#[cfg(all(feature = "disable_release", feature = "warn_release"))]
31compile_error!("disable_release cannot be active at the same time with warn_release");
32
33
34#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
35thread_local! {
36	static ALLOC_FORBID_COUNT: Cell<u32> = Cell::new(0);
37	static ALLOC_PERMIT_COUNT: Cell<u32> = Cell::new(0);
38
39	#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))]
40	static ALLOC_VIOLATION_COUNT: Cell<u32> = Cell::new(0);
41}
42
43#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
44pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
45	func()
46}
47
48#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
49pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
50	func()
51}
52
53#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
54/// Calls the `func` closure, but forbids any (de)allocations.
55///
56/// If a call to the allocator is made, the program will abort with an error,
57/// print a warning (depending on the `warn_debug` feature flag. Or ignore
58/// the situation, when compiled in `--release` mode with the `disable_release`
59///feature flag set (which is the default)).
60pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T {
61	// RAII guard for managing the forbid counter. This is to ensure correct behaviour
62	// when catch_unwind is used
63	struct Guard;
64	impl Guard {
65		fn new() -> Guard {
66			ALLOC_FORBID_COUNT.with(|c| c.set(c.get()+1));
67			Guard
68		}
69	}
70	impl Drop for Guard {
71		fn drop(&mut self) {
72			ALLOC_FORBID_COUNT.with(|c| c.set(c.get()-1));
73		}
74	}
75
76
77	#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
78	let old_violation_count = violation_count();
79
80
81	let guard = Guard::new(); // increment the forbid counter
82	let ret = func();
83	std::mem::drop(guard);    // decrement the forbid counter
84
85
86	#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
87	if violation_count() > old_violation_count {
88		eprintln!("Tried to (de)allocate memory in a thread that forbids allocator calls!");
89	}
90
91	return ret;
92}
93
94#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
95/// Calls the `func` closure. Allocations are temporarily allowed, even if this
96/// code runs inside of assert_no_alloc.
97pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T {
98	// RAII guard for managing the permit counter
99	struct Guard;
100	impl Guard {
101		fn new() -> Guard {
102			ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()+1));
103			Guard
104		}
105	}
106	impl Drop for Guard {
107		fn drop(&mut self) {
108			ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()-1));
109		}
110	}
111
112	let guard = Guard::new(); // increment the forbid counter
113	let ret = func();
114	std::mem::drop(guard);    // decrement the forbid counter
115
116	return ret;
117}
118
119#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
120/// Returns the count of allocation warnings emitted so far.
121///
122/// Only available when the `warn_debug` or `warn release` features are enabled.
123pub fn violation_count() -> u32 {
124	ALLOC_VIOLATION_COUNT.with(|c| c.get())
125}
126
127#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
128/// Resets the count of allocation warnings to zero.
129///
130/// Only available when the `warn_debug` or `warn release` features are enabled.
131pub fn reset_violation_count() {
132	ALLOC_VIOLATION_COUNT.with(|c| c.set(0));
133}
134
135
136
137
138#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
139/// The custom allocator that handles the checking.
140///
141/// To use this crate, you must add the following in your `main.rs`:
142/// ```rust
143/// use assert_no_alloc::*;
144/// // ...
145/// #[cfg(debug_assertions)]
146/// #[global_allocator]
147/// static A: AllocDisabler = AllocDisabler;
148/// ```
149pub struct AllocDisabler;
150
151#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
152impl AllocDisabler {
153	fn check(&self, layout: Layout) {
154		let forbid_count = ALLOC_FORBID_COUNT.with(|f| f.get());
155		let permit_count = ALLOC_PERMIT_COUNT.with(|p| p.get());
156		if forbid_count > 0 && permit_count == 0 {
157			#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
158			ALLOC_VIOLATION_COUNT.with(|c| c.set(c.get()+1));
159
160			#[cfg(any( all(not(feature="warn_debug"), debug_assertions), all(not(feature="warn_release"), not(debug_assertions)) ))] // if abort mode is selected
161			{
162				#[cfg(all(feature = "log", feature = "backtrace"))]
163				permit_alloc(|| log::error!("Memory allocation of {} bytes failed from:\n{:?}", layout.size(), backtrace::Backtrace::new()));
164				#[cfg(all(feature = "log", not(feature = "backtrace")))]
165				permit_alloc(|| log::error!("Memory allocation of {} bytes failed", layout.size()));
166
167				#[cfg(all(not(feature = "log"), feature = "backtrace"))]
168				permit_alloc(|| eprintln!("Allocation failure from:\n{:?}", backtrace::Backtrace::new()));
169
170				// This handler can be overridden (although as of writing, the API to do so is still
171				// unstable) so we must always call this even when the log feature is enabled
172				std::alloc::handle_alloc_error(layout);
173			}
174		}
175	}
176}
177
178#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
179unsafe impl GlobalAlloc for AllocDisabler {
180	unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
181		self.check(layout);
182		System.alloc(layout)
183	}
184
185	unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
186		System.dealloc(ptr, layout)
187	}
188}
189
190/// Wrapper for objects whose Drop implementation shall be permitted
191/// to (de)allocate.
192///
193/// Typical usage:
194///
195/// ```rust
196/// let foo = PermitDrop::new(
197///     permit_alloc(||
198///         Box::new(...)
199///     )
200/// );
201/// ```
202///
203/// Here, creation of the Box is guarded by the explicit `permit_alloc` call,
204/// and destruction of the Box is guarded by PermitDrop. Neither creation nor
205/// destruction will cause an assertion failure from within `assert_no_alloc`.
206pub struct PermitDrop<T>(Option<T>);
207
208impl<T> PermitDrop<T> {
209	pub fn new(t: T) -> PermitDrop<T> {
210		permit_alloc(|| {
211			PermitDrop(Some(t))
212		})
213	}
214}
215
216impl<T> std::ops::Deref for PermitDrop<T> {
217	type Target = T;
218	fn deref(&self) -> &T { self.0.as_ref().unwrap() }
219}
220
221impl<T> std::ops::DerefMut for PermitDrop<T> {
222	fn deref_mut(&mut self) -> &mut T { self.0.as_mut().unwrap() }
223}
224
225impl<I: Iterator> Iterator for PermitDrop<I> {
226	type Item = I::Item;
227	fn next(&mut self) -> Option<Self::Item> {
228		(**self).next()
229	}
230}
231
232
233impl<T> Drop for PermitDrop<T> {
234	fn drop(&mut self) {
235		let mut tmp = None;
236		std::mem::swap(&mut tmp, &mut self.0);
237		permit_alloc(|| {
238			std::mem::drop(tmp);
239		});
240	}
241}