diff --git a/.gitignore b/.gitignore index 6936990..2fcd2ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +*.swp diff --git a/src/lib.rs b/src/lib.rs index 53ece0d..cc88ad4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,18 +17,31 @@ //! 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 +//! ```rust //! use qadapt::QADAPT; //! //! #[global_allocator] //! static Q: QADAPT = QADAPT; +//! +//! fn main() { +//! # // Because `debug_assertions` are on for doctests in release mode +//! # // we have to add an extra guard. +//! # if qadapt::is_active() { +//! assert!(qadapt::is_active()); +//! # } +//! } //! ``` //! //! 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 +//! ```rust //! use qadapt::no_alloc; +//! use qadapt::QADAPT; +//! use std::panic::catch_unwind; +//! +//! #[global_allocator] +//! static Q: QADAPT = QADAPT; //! //! // This function is fine, there are no allocations here //! #[no_alloc] @@ -44,15 +57,23 @@ //! //! fn main() { //! do_math(); -//! does_panic(); +//! +//! let err = catch_unwind(|| does_panic()); +//! # if qadapt::is_active() { +//! assert!(err.is_err()); +//! # } //! } //! ``` //! //! 2. Evaluate expressions with the `assert_no_alloc!` macro -//! ```rust,no_run +//! ```rust //! use qadapt::assert_no_alloc; +//! use qadapt::QADAPT; //! -//! fn do_work() { +//! #[global_allocator] +//! static Q: QADAPT = QADAPT; +//! +//! fn main() { //! // This code is allowed to trigger an allocation //! let b = Box::new(8); //! @@ -60,6 +81,7 @@ //! let x = assert_no_alloc!(*b + 2); //! assert_eq!(x, 10); //! } +//! ``` #![deny(missing_docs)] // thread_id is necessary because `std::thread::current()` panics if we have not yet @@ -78,17 +100,25 @@ use std::thread; thread_local! { static PROTECTION_LEVEL: RwLock = RwLock::new(0); } +static IS_ACTIVE: RwLock = RwLock::new(false); +static INTERNAL_ALLOCATION: RwLock = RwLock::new(usize::max_value()); /// The QADAPT allocator itself /// /// To make use of the allocator, include this code block in your program /// binaries/tests: /// -/// ```rust,ignore +/// ```rust /// use qadapt::QADAPT; /// /// #[global_allocator] /// static Q: QADAPT = QADAPT; +/// +/// fn main() { +/// # if qadapt::is_active() { +/// assert!(qadapt::is_active()); +/// # } +/// } /// ``` pub struct QADAPT; @@ -99,9 +129,13 @@ static SYSTEM_ALLOC: System = System; /// /// **Example**: /// -/// ```rust,no_run +/// ```rust /// use qadapt::enter_protected; /// use qadapt::exit_protected; +/// use qadapt::QADAPT; +/// +/// #[global_allocator] +/// static Q: QADAPT = QADAPT; /// /// fn main() { /// // Force an allocation by using a Box @@ -119,14 +153,10 @@ static SYSTEM_ALLOC: System = System; pub fn enter_protected() { #[cfg(debug_assertions)] { - if thread::panicking() { + if thread::panicking() || !is_active() { return; } - if !*IS_ACTIVE.read() { - panic!("QADAPT not initialized when using allocation guards; please verify `#[global_allocator]` is set!"); - } - PROTECTION_LEVEL .try_with(|v| { *v.write() += 1; @@ -140,9 +170,13 @@ pub fn enter_protected() { /// /// **Example**: /// -/// ```rust,no_run +/// ```rust /// use qadapt::enter_protected; /// use qadapt::exit_protected; +/// use qadapt::QADAPT; +/// +/// #[global_allocator] +/// static Q: QADAPT = QADAPT; /// /// fn main() { /// // Force an allocation by using a Box @@ -160,7 +194,7 @@ pub fn enter_protected() { pub fn exit_protected() { #[cfg(debug_assertions)] { - if thread::panicking() { + if thread::panicking() || !is_active() { return; } @@ -183,8 +217,12 @@ pub fn exit_protected() { /// /// **Example**: /// -/// ```rust,no_run +/// ```rust /// use qadapt::assert_no_alloc; +/// use qadapt::QADAPT; +/// +/// #[global_allocator] +/// static Q: QADAPT = QADAPT; /// /// fn main() { /// assert_no_alloc!(2 + 2); @@ -196,8 +234,13 @@ pub fn exit_protected() { /// in code that was not intended to be allocation-free. The compiler will warn you /// that there is an unreachable statement if this happens. /// -/// ```rust,no_run +/// ```rust /// use qadapt::assert_no_alloc; +/// use qadapt::QADAPT; +/// use std::panic::catch_unwind; +/// +/// #[global_allocator] +/// static Q: QADAPT = QADAPT; /// /// fn early_return() -> usize { /// assert_no_alloc!(return 8); @@ -206,10 +249,14 @@ pub fn exit_protected() { /// fn main() { /// let x = early_return(); /// -/// // This triggers a panic - `Box::new` forces an allocation, -/// // and QADAPT still thinks we're in a protected region because -/// // of a return in the `early_return()` function -/// let b = Box::new(x); +/// // Even though only the `early_return` function contains +/// // QADAPT allocation guards, this triggers a panic: +/// // `Box::new` forces an allocation, and QADAPT still thinks +/// // we're in a protected region because of the return in `early_return()` +/// # if qadapt::is_active() { +/// let res = catch_unwind(|| Box::new(x)); +/// assert!(res.is_err()); +/// # } /// } #[macro_export] macro_rules! assert_no_alloc { @@ -221,19 +268,23 @@ macro_rules! assert_no_alloc { }}; } -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() +/// Get the current "protection level" in QADAPT: calls to `enter_protected() - exit_protected()`. +/// +/// **Note**: For release builds, `protection_level()` will always return 0. /// /// **Example**: /// -/// ```rust,no_run +/// ```rust /// use qadapt::enter_protected; /// use qadapt::exit_protected; +/// use qadapt::QADAPT; /// use qadapt::protection_level; /// +/// #[global_allocator] +/// static Q: QADAPT = QADAPT; +/// /// fn main() { +/// # if qadapt::is_active() { /// enter_protected(); /// // We're now in an allocation-protected code region /// assert_eq!(1, protection_level()); @@ -243,14 +294,44 @@ static INTERNAL_ALLOCATION: RwLock = RwLock::new(usize::max_value()); /// assert_eq!(2, protection_level()); /// exit_protected(); /// exit_protected(); +/// # } /// /// // It's now safe to allocate/drop /// } pub fn protection_level() -> usize { + PROTECTION_LEVEL.try_with(|v| *v.read()).unwrap_or(0) +} + +/// Determine whether QADAPT will trigger thread panics if an allocation happens +/// during protected code. This should be used for making sure that QADAPT is +/// properly set up and initialized. +/// +/// Note that this will return `false` in release builds even if QADAPT is set +/// as the `#[global_allocator]`. +/// +/// **Example**: +/// +/// ```rust +/// use qadapt::is_active; +/// use qadapt::QADAPT; +/// +/// #[global_allocator] +/// static Q: QADAPT = QADAPT; +/// +/// pub fn main() { +/// # if qadapt::is_active() { +/// assert!(is_active()); +/// # } +/// } +/// ``` +pub fn is_active() -> bool { if cfg!(debug_assertions) { - PROTECTION_LEVEL.try_with(|v| *v.read()).unwrap_or(0) + // Because there are heap allocations that happen before `fn main()`, + // we don't need to force an extra allocation here to guarantee that + // IS_ACTIVE is set + *IS_ACTIVE.read() } else { - 0 + false } } diff --git a/tests/inactive.rs b/tests/inactive.rs new file mode 100644 index 0000000..cf08118 --- /dev/null +++ b/tests/inactive.rs @@ -0,0 +1,4 @@ +#[test] +fn is_inactive() { + assert!(!qadapt::is_active()); +} diff --git a/tests/inactive_release.rs b/tests/inactive_release.rs new file mode 100644 index 0000000..05a5627 --- /dev/null +++ b/tests/inactive_release.rs @@ -0,0 +1,10 @@ +use qadapt::QADAPT; + +#[global_allocator] +static Q: QADAPT = QADAPT; + +#[cfg(not(debug_assertions))] +#[test] +fn release_only_inactive() { + assert!(!qadapt::is_active()); +} diff --git a/tests/unused_panic.rs b/tests/unused_panic.rs deleted file mode 100644 index e185a82..0000000 --- a/tests/unused_panic.rs +++ /dev/null @@ -1,11 +0,0 @@ -use qadapt::enter_protected; - -#[test] -#[should_panic] -fn guard_without_initialization() { - if cfg!(debug_assertions) { - enter_protected(); - } else { - panic!("Intentional") - } -}