From 65673e1af2b34bec6eebb492372b8a8178c10f82 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Thu, 6 Dec 2018 23:02:44 -0500 Subject: [PATCH] Renaming and a new macro --- README.md | 56 ++++++++++++++++--- examples/release_mode.rs | 4 +- examples/setup_warning.rs | 20 ------- qadapt-macro/src/lib.rs | 2 +- src/lib.rs | 90 +++++++++++++++++++++++------- tests/assert_macro.rs | 26 +++++++++ tests/{macros.rs => proc_macro.rs} | 22 ++++---- tests/unused_panic.rs | 7 +++ 8 files changed, 164 insertions(+), 63 deletions(-) delete mode 100644 examples/setup_warning.rs create mode 100644 tests/assert_macro.rs rename tests/{macros.rs => proc_macro.rs} (93%) create mode 100644 tests/unused_panic.rs diff --git a/README.md b/README.md index 625faf2..30d8f73 100644 --- a/README.md +++ b/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); +} diff --git a/examples/release_mode.rs b/examples/release_mode.rs index b2a8156..027f6ac 100644 --- a/examples/release_mode.rs +++ b/examples/release_mode.rs @@ -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 { Box::new(0) } diff --git a/examples/setup_warning.rs b/examples/setup_warning.rs deleted file mode 100644 index 28bcb0a..0000000 --- a/examples/setup_warning.rs +++ /dev/null @@ -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 { - 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(); -} diff --git a/qadapt-macro/src/lib.rs b/qadapt-macro/src/lib.rs index 1d53632..4f14c75 100644 --- a/qadapt-macro/src/lib.rs +++ b/qadapt-macro/src/lib.rs @@ -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 = Vec::new(); let mut item_iter = item.into_iter(); diff --git a/src/lib.rs b/src/lib.rs index fb69128..82e6377 100644 --- a/src/lib.rs +++ b/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 = RwLock::new(false); static INTERNAL_ALLOCATION: RwLock = 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 = 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!( diff --git a/tests/assert_macro.rs b/tests/assert_macro.rs new file mode 100644 index 0000000..78b7e9f --- /dev/null +++ b/tests/assert_macro.rs @@ -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 { + Box::new(early_return()) +} + +#[test] +#[should_panic] +fn early_return_boxing() { + into_box(); +} + diff --git a/tests/macros.rs b/tests/proc_macro.rs similarity index 93% rename from tests/macros.rs rename to tests/proc_macro.rs index 57f0ed6..7e3fddd 100644 --- a/tests/macros.rs +++ b/tests/proc_macro.rs @@ -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) -> Result, ()> { 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)] { diff --git a/tests/unused_panic.rs b/tests/unused_panic.rs new file mode 100644 index 0000000..e378e0a --- /dev/null +++ b/tests/unused_panic.rs @@ -0,0 +1,7 @@ +use qadapt::enter_protected; + +#[test] +#[should_panic] +fn guard_without_initialization() { + enter_protected(); +} \ No newline at end of file