mirror of
				https://github.com/bspeice/qadapt
				synced 2025-10-30 17:10:36 -04:00 
			
		
		
		
	Renaming and a new macro
This commit is contained in:
		
							
								
								
									
										56
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								README.md
									
									
									
									
									
								
							| @ -8,15 +8,53 @@ | ||||
|  | ||||
|  | ||||
| --- | ||||
| # The Quick And Dirty Allocation Profiling Tool | ||||
| # QADAPT - `debug_assert!` for your memory | ||||
|  | ||||
| This allocator is a helper for writing high-performance code that is allocation/drop free; | ||||
| for functions annotated with `#[allocate_panic]`, QADAPT will detect when allocations/drops | ||||
| happen during their execution (and execution of any functions they call) and throw a | ||||
| thread panic if this occurs. QADAPT-related code is *stripped out during release builds*, | ||||
| so no worries about random allocations crashing in production. | ||||
| This allocator is a helper for writing high-performance code that is memory-sensitive; | ||||
| a thread panic will be triggered if a function annotated with `#[no_alloc]`, | ||||
| or code inside an `assert_no_alloc!` macro interacts with the allocator in any way. | ||||
| Wanton allocations and unforeseen drops no more - this library lets you focus on | ||||
| writing code without worrying if Rust properly managed to inline the variable into the stack. | ||||
|  | ||||
| Currently this crate is Nightly-only, but will work once `const fn` is in Stable. | ||||
| Now, an allocator blowing up in production is a scary thought; that's why QADAPT | ||||
| is designed to strip its own code out whenever you're running with a release build. | ||||
| Just like the [`debug_assert!` macro](https://doc.rust-lang.org/std/macro.debug_assert.html) | ||||
| in Rust's standard library, it's safe to use without worrying about a unforeseen | ||||
| circumstance causing your application to crash. | ||||
|  | ||||
| Please also take a look at [qadapt-macro](https://github.com/bspeice/qadapt/tree/master/qadapt-macro) | ||||
| for some helper macros to make working with QADAPT a bit easier. | ||||
| # Usage | ||||
|  | ||||
| Actually making use of QADAPT is straight-forward. To set up the allocator, | ||||
| place the following snippet in either your program binaries (main.rs) or tests: | ||||
|  | ||||
| ```rust,ignore | ||||
| use qadapt::QADAPT; | ||||
|  | ||||
| #[global_allocator] | ||||
| static Q: QADAPT = QADAPT; | ||||
| ``` | ||||
|  | ||||
| After that, there are two ways of telling QADAPT that it should trigger a panic: | ||||
|  | ||||
| 1. Annotate functions with the `#[no_alloc]` proc macro: | ||||
| ```rust,no_run | ||||
| use qadapt::no_alloc; | ||||
|  | ||||
| #[no_alloc] | ||||
| fn do_math() -> u8 { | ||||
| 2 + 2 | ||||
| } | ||||
| ``` | ||||
|  | ||||
| 2. Evaluate expressions with the `assert_no_alloc!` macro | ||||
| ```rust,no_run | ||||
| use qadapt::assert_no_alloc; | ||||
|  | ||||
| fn do_work() { | ||||
| // This code is allowed to trigger an allocation | ||||
| let b = Box::new(8); | ||||
|  | ||||
| // This code would panic if an allocation occurred inside it | ||||
| let x = assert_no_alloc!(*b + 2); | ||||
| assert_eq!(x, 10); | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| use qadapt::allocate_panic; | ||||
| use qadapt::no_alloc; | ||||
| use qadapt::QADAPT; | ||||
|  | ||||
| #[global_allocator] | ||||
| static Q: QADAPT = QADAPT; | ||||
|  | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn does_allocate() -> Box<u8> { | ||||
|     Box::new(0) | ||||
| } | ||||
|  | ||||
| @ -1,20 +0,0 @@ | ||||
| use env_logger; | ||||
|  | ||||
| use qadapt::allocate_panic; | ||||
|  | ||||
| // Note that we're missing the `#[global_allocator]` attribute | ||||
|  | ||||
| #[allocate_panic] | ||||
| fn does_allocate() -> Box<u8> { | ||||
|     Box::new(0) | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
|     // This code will warn that QADAPT isn't being used, but won't trigger a panic. | ||||
|     // Run with `RUST_LOG=warn cargo run --example setup_warning` | ||||
|     env_logger::init(); | ||||
|     does_allocate(); | ||||
|  | ||||
|     // The warning will only trigger once though | ||||
|     does_allocate(); | ||||
| } | ||||
| @ -155,7 +155,7 @@ fn escape_return(ts: TokenStream) -> TokenStream { | ||||
| /// separate thread, or defers allocations via closure/Future, those results | ||||
| /// will not trigger an error. | ||||
| #[proc_macro_attribute] | ||||
| pub fn allocate_panic(_attr: TokenStream, item: TokenStream) -> TokenStream { | ||||
| pub fn no_alloc(_attr: TokenStream, item: TokenStream) -> TokenStream { | ||||
|     let mut protected_fn: Vec<TokenTree> = Vec::new(); | ||||
|     let mut item_iter = item.into_iter(); | ||||
|  | ||||
|  | ||||
							
								
								
									
										90
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								src/lib.rs
									
									
									
									
									
								
							| @ -1,15 +1,53 @@ | ||||
| //! # The Quick And Dirty Allocation Profiling Tool | ||||
| //! # QADAPT - `debug_assert!` for your memory | ||||
| //! | ||||
| //! This allocator is a helper for writing high-performance code that is allocation/drop free; | ||||
| //! for functions annotated with `#[allocate_panic]`, QADAPT will detect when allocations/drops | ||||
| //! happen during their execution (and execution of any functions they call) and throw a | ||||
| //! thread panic if this occurs. QADAPT-related code is *stripped out during release builds*, | ||||
| //! so no worries about random allocations crashing in production. | ||||
| //! | ||||
| //! Currently this crate is Nightly-only, but will work once `const fn` is in Stable. | ||||
| //! | ||||
| //! Please also take a look at [qadapt-macro](https://github.com/bspeice/qadapt/tree/master/qadapt-macro) | ||||
| //! for some helper macros to make working with QADAPT a bit easier. | ||||
| //! This allocator is a helper for writing high-performance code that is memory-sensitive; | ||||
| //! a thread panic will be triggered if a function annotated with `#[no_alloc]`, | ||||
| //! or code inside an `assert_no_alloc!` macro interacts with the allocator in any way. | ||||
| //! Wanton allocations and unforeseen drops no more - this library lets you focus on | ||||
| //! writing code without worrying if Rust properly managed to inline the variable into the stack. | ||||
| //!  | ||||
| //! Now, an allocator blowing up in production is a scary thought; that's why QADAPT | ||||
| //! is designed to strip its own code out whenever you're running with a release build. | ||||
| //! Just like the [`debug_assert!` macro](https://doc.rust-lang.org/std/macro.debug_assert.html) | ||||
| //! in Rust's standard library, it's safe to use without worrying about a unforeseen | ||||
| //! circumstance causing your application to crash. | ||||
| //!  | ||||
| //! # Usage | ||||
| //!  | ||||
| //! Actually making use of QADAPT is straight-forward. To set up the allocator, | ||||
| //! place the following snippet in either your program binaries (main.rs) or tests: | ||||
| //!  | ||||
| //! ```rust,ignore | ||||
| //! use qadapt::QADAPT; | ||||
| //!  | ||||
| //! #[global_allocator] | ||||
| //! static Q: QADAPT = QADAPT; | ||||
| //! ``` | ||||
| //!  | ||||
| //! After that, there are two ways of telling QADAPT that it should trigger a panic: | ||||
| //!  | ||||
| //! 1. Annotate functions with the `#[no_alloc]` proc macro: | ||||
| //! ```rust,no_run | ||||
| //! use qadapt::no_alloc; | ||||
| //!  | ||||
| //! #[no_alloc] | ||||
| //! fn do_math() -> u8 { | ||||
| //!     2 + 2 | ||||
| //! } | ||||
| //! ``` | ||||
| //!  | ||||
| //! 2. Evaluate expressions with the `assert_no_alloc!` macro | ||||
| //! ```rust,no_run | ||||
| //! use qadapt::assert_no_alloc; | ||||
| //!  | ||||
| //! fn do_work() { | ||||
| //!     // This code is allowed to trigger an allocation | ||||
| //!     let b = Box::new(8); | ||||
| //!      | ||||
| //!     // This code would panic if an allocation occurred inside it | ||||
| //!     let x = assert_no_alloc!(*b + 2); | ||||
| //!     assert_eq!(x, 10); | ||||
| //! } | ||||
| #![deny(missing_docs)] | ||||
|  | ||||
| use log::warn; | ||||
| @ -45,8 +83,7 @@ pub fn enter_protected() { | ||||
|         } | ||||
|  | ||||
|         if !*IS_ACTIVE.read() { | ||||
|             *IS_ACTIVE.write() = true; | ||||
|             warn!("QADAPT not initialized when using allocation guards; please verify `#[global_allocator]` is set!"); | ||||
|             panic!("QADAPT not initialized when using allocation guards; please verify `#[global_allocator]` is set!"); | ||||
|         } | ||||
|  | ||||
|         PROTECTION_LEVEL | ||||
| @ -80,17 +117,30 @@ pub fn exit_protected() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Get the result of an expression, guaranteeing that no allocations occur | ||||
| /// during its evaluation. | ||||
| ///  | ||||
| /// **Warning**: Unexpected behavior may occur when using the `return` keyword. | ||||
| /// Because the macro cleanup logic will not be run, QADAPT may trigger a panic | ||||
| /// in code that was not specifically intended to be allocation-free. | ||||
| #[macro_export] | ||||
| macro_rules! assert_no_alloc { | ||||
|     ($e:expr) => {{ | ||||
|         ::qadapt::enter_protected(); | ||||
|         let e = { $e }; | ||||
|         ::qadapt::exit_protected(); | ||||
|         e | ||||
|     }}; | ||||
| } | ||||
|  | ||||
| static IS_ACTIVE: RwLock<bool> = RwLock::new(false); | ||||
| static INTERNAL_ALLOCATION: RwLock<usize> = RwLock::new(usize::max_value()); | ||||
|  | ||||
| /// Get the current "protection level" in QADAPT: calls to enter_protected() - exit_protected() | ||||
| pub fn protection_level() -> usize { | ||||
|     #[cfg(debug_assertions)] | ||||
|     { | ||||
|     if cfg!(debug_assertions) { | ||||
|         PROTECTION_LEVEL.try_with(|v| *v.read()).unwrap_or(0) | ||||
|     } | ||||
|     #[cfg(not(debug_assertions))] | ||||
|     { | ||||
|     } else { | ||||
|         0 | ||||
|     } | ||||
| } | ||||
| @ -131,7 +181,7 @@ unsafe impl GlobalAlloc for QADAPT { | ||||
|         } | ||||
|  | ||||
|         // Because accessing PROTECTION_LEVEL has the potential to trigger an allocation, | ||||
|         // we need to spin until we can claim the INTERNAL_ALLOCATION lock for our thread. | ||||
|         // we need to acquire the INTERNAL_ALLOCATION lock for our thread. | ||||
|         claim_internal_alloc(); | ||||
|         let protection_level: Result<usize, ()> = | ||||
|             PROTECTION_LEVEL.try_with(|v| *v.read()).or(Ok(0)); | ||||
| @ -167,7 +217,7 @@ unsafe impl GlobalAlloc for QADAPT { | ||||
|         free(ptr as *mut c_void); | ||||
|         match protection_level { | ||||
|             Ok(v) if v > 0 => { | ||||
|                 // Tripped a bad dealloc, but make sure further memory access during unwind | ||||
|                 // Tripped a bad drop, but make sure further memory access during unwind | ||||
|                 // doesn't have issues | ||||
|                 PROTECTION_LEVEL.with(|v| *v.write() = 0); | ||||
|                 panic!( | ||||
|  | ||||
							
								
								
									
										26
									
								
								tests/assert_macro.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								tests/assert_macro.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| use qadapt::assert_no_alloc; | ||||
| use qadapt::QADAPT; | ||||
|  | ||||
| #[global_allocator] | ||||
| static Q: QADAPT = QADAPT; | ||||
|  | ||||
| #[test] | ||||
| fn math() { | ||||
|     let x = assert_no_alloc!(2 + 2); | ||||
|     assert_eq!(x, 4); | ||||
| } | ||||
|  | ||||
| fn early_return() -> usize { | ||||
|     assert_no_alloc!(return 8) | ||||
| } | ||||
|  | ||||
| fn into_box() -> Box<usize> { | ||||
|     Box::new(early_return()) | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| #[should_panic] | ||||
| fn early_return_boxing() { | ||||
|     into_box(); | ||||
| } | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| use std::io; | ||||
| 
 | ||||
| use qadapt::allocate_panic; | ||||
| use qadapt::no_alloc; | ||||
| use qadapt::QADAPT; | ||||
| 
 | ||||
| #[global_allocator] | ||||
| static Q: QADAPT = QADAPT; | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn no_allocate() { | ||||
|     let _v: Vec<()> = Vec::with_capacity(0); | ||||
| } | ||||
| @ -16,7 +16,7 @@ fn macro_no_allocate() { | ||||
|     no_allocate(); | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn allocates() { | ||||
|     assert_eq!(::qadapt::protection_level(), 1); | ||||
|     // Without boxing, release profile can actually optimize out the allocation
 | ||||
| @ -30,7 +30,7 @@ fn macro_allocates() { | ||||
|     allocates(); | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn no_allocate_ret() -> bool { | ||||
|     return true; | ||||
| } | ||||
| @ -40,7 +40,7 @@ fn macro_return() { | ||||
|     assert!(no_allocate_ret()); | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn no_allocate_implicit_ret() -> bool { | ||||
|     true | ||||
| } | ||||
| @ -50,7 +50,7 @@ fn macro_implicit_return() { | ||||
|     assert!(no_allocate_implicit_ret()); | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn no_allocate_arg(b: bool) -> bool { | ||||
|     b | ||||
| } | ||||
| @ -61,7 +61,7 @@ fn macro_allocate_arg() { | ||||
|     no_allocate_arg(false); | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn no_allocate_args(_b: bool, _u: usize, i: i64) -> i64 { | ||||
|     i | ||||
| } | ||||
| @ -72,7 +72,7 @@ fn macro_allocate_args() { | ||||
|     no_allocate_args(false, 4, -90); | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn return_result(r: Result<usize, io::Error>) -> Result<Result<usize, io::Error>, ()> { | ||||
|     Ok(r) | ||||
| } | ||||
| @ -82,7 +82,7 @@ fn macro_return_result() { | ||||
|     return_result(Ok(16)).unwrap().unwrap(); | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn branching_return(a: bool, b: bool, c: bool) -> u8 { | ||||
|     if a { | ||||
|         if b { | ||||
| @ -131,7 +131,7 @@ fn run_closure(x: impl Fn(bool, bool) -> bool) -> bool { | ||||
|     x(true, false) | ||||
| } | ||||
| 
 | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn example_closure() { | ||||
|     let c = run_closure(|a: bool, b| return a && b); | ||||
|     assert!(!c); | ||||
| @ -145,7 +145,7 @@ fn macro_closure() { | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| #[allocate_panic] | ||||
| #[no_alloc] | ||||
| fn macro_release_safe() { | ||||
|     #[cfg(debug_assertions)] | ||||
|     { | ||||
							
								
								
									
										7
									
								
								tests/unused_panic.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/unused_panic.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| use qadapt::enter_protected; | ||||
|  | ||||
| #[test] | ||||
| #[should_panic] | ||||
| fn guard_without_initialization() { | ||||
|     enter_protected(); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user