mirror of
https://github.com/urbit/ares.git
synced 2024-11-26 09:57:56 +03:00
wip: add rust-assert-no-alloc
This commit is contained in:
parent
680d2d254c
commit
dd70aadf6e
@ -1 +0,0 @@
|
||||
Subproject commit 11f0f41123f7fcaf0ad1c23a6a17d97f4650e824
|
2
rust/rust-assert-no-alloc/.gitignore
vendored
Normal file
2
rust/rust-assert-no-alloc/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
32
rust/rust-assert-no-alloc/Cargo.toml
Normal file
32
rust/rust-assert-no-alloc/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "assert_no_alloc"
|
||||
version = "1.1.2"
|
||||
authors = ["Florian Jung <flo@windfis.ch>"]
|
||||
edition = "2018"
|
||||
license = "BSD-1-Clause"
|
||||
description = "Custom Rust allocator allowing to temporarily disable memory (de)allocations for a thread. Aborts or prints a warning if allocating although forbidden."
|
||||
homepage = "https://github.com/Windfisch/rust-assert-no-alloc"
|
||||
repository = "https://github.com/Windfisch/rust-assert-no-alloc"
|
||||
readme = "README.md"
|
||||
keywords = ["allocator", "real-time", "debug", "audio"]
|
||||
categories = ["development-tools::debugging"]
|
||||
|
||||
[features]
|
||||
default = ["disable_release"]
|
||||
warn_debug = []
|
||||
warn_release = []
|
||||
disable_release = []
|
||||
|
||||
# Print a backtrace before aborting the program when an allocation failure happens
|
||||
backtrace = ["dep:backtrace"]
|
||||
# Use the `log` crate instead of printing to STDERR
|
||||
# WARNING: If the allocation failure happens during a logger call, then
|
||||
# depending on the logger's implementation this may block indefinitely
|
||||
log = ["dep:log"]
|
||||
|
||||
[dependencies]
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["warn_debug"]
|
21
rust/rust-assert-no-alloc/LICENSE
Normal file
21
rust/rust-assert-no-alloc/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
assert_no_alloc -- A custom Rust allocator allowing to temporarily disable
|
||||
memory (de)allocations for a thread.
|
||||
|
||||
Copyright (c) 2020 Florian Jung <flo@windfis.ch>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
143
rust/rust-assert-no-alloc/README.md
Normal file
143
rust/rust-assert-no-alloc/README.md
Normal file
@ -0,0 +1,143 @@
|
||||
assert_no_alloc
|
||||
===============
|
||||
|
||||
This crate provides a custom allocator that allows to temporarily disable
|
||||
memory (de)allocations for a thread. If a (de)allocation is attempted
|
||||
anyway, the program will abort or print a warning.
|
||||
|
||||
It uses thread local storage for the "disabled-flag/counter", and thus
|
||||
should be thread safe, if the underlying allocator (currently hard-coded
|
||||
to `std::alloc::System`) is.
|
||||
|
||||
[documentation @ docs.rs](https://docs.rs/assert_no_alloc/1.1.0/assert_no_alloc/),
|
||||
[crates.io](https://crates.io/crates/assert_no_alloc)
|
||||
|
||||
Rationale
|
||||
---------
|
||||
|
||||
No-allocation-zones are relevant e.g. in real-time scenarios like audio
|
||||
callbacks. Allocation and deallocation can take unpredictable amounts of
|
||||
time, and thus can *sometimes* lead to audible glitches because the audio
|
||||
data is not served in time.
|
||||
|
||||
Debugging such problems can be hard, because it is difficult to reproduce
|
||||
such problems consistently. Avoiding such problems is also hard, since
|
||||
allocation/deallocation is a common thing to do and most libraries are not
|
||||
explicit whether certain functions can allocate or not. Also, this might
|
||||
even depend on the run-time situation (e.g. a `Vec::push` might allocate,
|
||||
but it is guaranteed to not allocate *if* enough space has been `reserve()`d
|
||||
before).
|
||||
|
||||
To aid the developer in tackling these problems, this crate offers an easy
|
||||
way of detecting all forbidden allocations.
|
||||
|
||||
How to use
|
||||
----------
|
||||
|
||||
First, configure the features: `warn_debug` and `warn_release` change the
|
||||
behaviour from aborting your program into just printing an error message
|
||||
on `stderr`. Aborting is useful for debugging purposes, as it allows you
|
||||
to retrieve a stacktrace, while warning is less intrusive.
|
||||
|
||||
Note that you need to disable the (default-enabled) `disable_release` feature
|
||||
by specify `default-features = false` if you want to use `warn_release`. If
|
||||
`disable_release` is set (which is the default), then this crate will do
|
||||
nothing if built in `--release` mode.
|
||||
|
||||
Second, use the allocator provided by this crate. Add this to `main.rs`:
|
||||
|
||||
```rust
|
||||
use assert_no_alloc::*;
|
||||
|
||||
#[cfg(debug_assertions)] // required when disable_release is set (default)
|
||||
#[global_allocator]
|
||||
static A: AllocDisabler = AllocDisabler;
|
||||
```
|
||||
|
||||
Third, wrap code sections that may not allocate like this:
|
||||
|
||||
```rust
|
||||
assert_no_alloc(|| {
|
||||
println!("This code can not allocate.");
|
||||
});
|
||||
```
|
||||
|
||||
Advanced use
|
||||
------------
|
||||
|
||||
Values can be returned using:
|
||||
|
||||
```rust
|
||||
let answer = assert_no_alloc(|| { 42 });
|
||||
```
|
||||
|
||||
The effect of `assert_no_alloc` can be overridden using `permit_alloc`:
|
||||
|
||||
```rust
|
||||
assert_no_alloc(|| {
|
||||
permit_alloc(|| {
|
||||
// Allocate some memory here. This will work.
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This is useful for test stubs whose code is executed in an `assert_no_alloc`
|
||||
context.
|
||||
|
||||
Objects that deallocate upon `Drop` can be wrapped in `PermitDrop`:
|
||||
|
||||
```rust
|
||||
let foo = PermitDrop::new(
|
||||
permit_alloc(||
|
||||
Box::new(...)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Dropping `foo` will not trigger an assertion (but dropping a `Box` would).
|
||||
|
||||
`assert_no_alloc()` calls can be nested, with proper panic unwinding handling.
|
||||
|
||||
Note that to fully bypass this crate, e.g. when in release mode, you need to
|
||||
*both* have the `disable_release` feature flag enabled (which it is by default)
|
||||
and to not register `AllocDisabler` as `global_allocator`.
|
||||
|
||||
Optional features
|
||||
-----------------
|
||||
|
||||
These compile time features are not enabled by default:
|
||||
|
||||
- `backtrace` causes a backtrace to be printed before the allocation failure.
|
||||
This backtrace is gathered at runtime, and its accuracy depends on the
|
||||
platform and the compilation options used.
|
||||
- `log` uses the `log` crate to write the allocation failure message to the
|
||||
configured logger. If the `backtrace` feature is also enabled, then the
|
||||
backtrace will also be written to the logger This can be useful when using a
|
||||
logger that writes directly to a file or any other place that isn't STDERR.
|
||||
|
||||
The main caveat here is that if the allocation was caused by the logger and if
|
||||
the logger wraps its entire log function in a regular non-entrant mutex, then
|
||||
this may result in a deadlock. Make sure your logger doesn't do this before
|
||||
enabling this feature.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See [examples/main.rs](https://github.com/Windfisch/rust-assert-no-alloc/blob/master/examples/main.rs) for an example.
|
||||
|
||||
You can try out the different feature flags:
|
||||
|
||||
- `cargo run --example main` -> memory allocation of 4 bytes failed. Aborted (core dumped)
|
||||
- `cargo run --example main --release --no-default-features` -> same as above.
|
||||
- `cargo run --example main --features=warn_debug` -> Tried to (de)allocate memory in a thread that forbids allocator calls! This will not be executed if the above allocation has aborted.
|
||||
- `cargo run --example main --features=warn_release --release --no-default-features` -> same as above.
|
||||
- `cargo run --example main --release` will not even check for forbidden allocations
|
||||
|
||||
Test suite
|
||||
----------
|
||||
|
||||
The tests will fail to compile with the default features. Run them using:
|
||||
|
||||
```
|
||||
cargo test --features=warn_debug --tests
|
||||
```
|
34
rust/rust-assert-no-alloc/examples/main.rs
Normal file
34
rust/rust-assert-no-alloc/examples/main.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use assert_no_alloc::*;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[global_allocator]
|
||||
static A: AllocDisabler = AllocDisabler;
|
||||
|
||||
fn main() {
|
||||
println!("Alloc is allowed. Let's allocate some memory...");
|
||||
let mut vec_can_push = Vec::new();
|
||||
vec_can_push.push(42);
|
||||
|
||||
println!();
|
||||
|
||||
let fib5 = assert_no_alloc(|| {
|
||||
println!("Alloc is forbidden. Let's calculate something without memory allocations...");
|
||||
|
||||
fn fib(n: u32) -> u32 {
|
||||
if n<=1 { 1 }
|
||||
else { fib(n-1) + fib(n-2) }
|
||||
}
|
||||
|
||||
fib(5)
|
||||
});
|
||||
println!("\tSuccess, the 5th fibonacci number is {}", fib5);
|
||||
println!();
|
||||
|
||||
assert_no_alloc(|| {
|
||||
println!("Alloc is forbidden. Let's allocate some memory...");
|
||||
let mut vec_cannot_push = Vec::new();
|
||||
vec_cannot_push.push(42); // panics
|
||||
});
|
||||
|
||||
println!("This will not be executed if the above allocation has aborted.");
|
||||
}
|
259
rust/rust-assert-no-alloc/src/lib.rs
Normal file
259
rust/rust-assert-no-alloc/src/lib.rs
Normal file
@ -0,0 +1,259 @@
|
||||
/* assert_no_alloc -- A custom Rust allocator allowing to temporarily disable
|
||||
* memory (de)allocations for a thread.
|
||||
*
|
||||
* Copyright (c) 2020 Florian Jung <flo@windfis.ch>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::alloc::{System,GlobalAlloc,Layout};
|
||||
use std::cell::Cell;
|
||||
|
||||
// check for mutually exclusive features.
|
||||
#[cfg(all(feature = "disable_release", feature = "warn_release"))]
|
||||
compile_error!("disable_release cannot be active at the same time with warn_release");
|
||||
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
thread_local! {
|
||||
static ALLOC_FORBID_COUNT: Cell<u32> = Cell::new(0);
|
||||
static ALLOC_PERMIT_COUNT: Cell<u32> = Cell::new(0);
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))]
|
||||
static ALLOC_VIOLATION_COUNT: Cell<u32> = Cell::new(0);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
|
||||
pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
|
||||
func()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
|
||||
pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
|
||||
func()
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
/// Calls the `func` closure, but forbids any (de)allocations.
|
||||
///
|
||||
/// If a call to the allocator is made, the program will abort with an error,
|
||||
/// print a warning (depending on the `warn_debug` feature flag. Or ignore
|
||||
/// the situation, when compiled in `--release` mode with the `disable_release`
|
||||
///feature flag set (which is the default)).
|
||||
pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T {
|
||||
// RAII guard for managing the forbid counter. This is to ensure correct behaviour
|
||||
// when catch_unwind is used
|
||||
struct Guard;
|
||||
impl Guard {
|
||||
fn new() -> Guard {
|
||||
ALLOC_FORBID_COUNT.with(|c| c.set(c.get()+1));
|
||||
Guard
|
||||
}
|
||||
}
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
ALLOC_FORBID_COUNT.with(|c| c.set(c.get()-1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
let old_violation_count = violation_count();
|
||||
|
||||
|
||||
let guard = Guard::new(); // increment the forbid counter
|
||||
let ret = func();
|
||||
std::mem::drop(guard); // decrement the forbid counter
|
||||
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
if violation_count() > old_violation_count {
|
||||
eprintln!("Tried to (de)allocate memory in a thread that forbids allocator calls!");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
/// Calls the `func` closure, but ensures that the forbid and permit counters
|
||||
/// are maintained accurately even if a longjmp originates and terminates
|
||||
/// within the closure. If you longjmp over this function, we can't fix
|
||||
/// anything about it.
|
||||
pub fn ensure_alloc_counters<T, F: FnOnce() -> T> (func: F) -> T {
|
||||
let forbid_counter = ALLOC_FORBID_COUNT.with(|c| c.get());
|
||||
let permit_counter = ALLOC_PERMIT_COUNT.with(|c| c.get());
|
||||
|
||||
let ret = func();
|
||||
|
||||
ALLOC_FORBID_COUNT.with(|c| c.set(forbid_counter));
|
||||
ALLOC_PERMIT_COUNT.with(|c| c.set(permit_counter));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
/// Calls the `func` closure. Allocations are temporarily allowed, even if this
|
||||
/// code runs inside of assert_no_alloc.
|
||||
pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T {
|
||||
// RAII guard for managing the permit counter
|
||||
struct Guard;
|
||||
impl Guard {
|
||||
fn new() -> Guard {
|
||||
ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()+1));
|
||||
Guard
|
||||
}
|
||||
}
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()-1));
|
||||
}
|
||||
}
|
||||
|
||||
let guard = Guard::new(); // increment the forbid counter
|
||||
let ret = func();
|
||||
std::mem::drop(guard); // decrement the forbid counter
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
/// Returns the count of allocation warnings emitted so far.
|
||||
///
|
||||
/// Only available when the `warn_debug` or `warn release` features are enabled.
|
||||
pub fn violation_count() -> u32 {
|
||||
ALLOC_VIOLATION_COUNT.with(|c| c.get())
|
||||
}
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
/// Resets the count of allocation warnings to zero.
|
||||
///
|
||||
/// Only available when the `warn_debug` or `warn release` features are enabled.
|
||||
pub fn reset_violation_count() {
|
||||
ALLOC_VIOLATION_COUNT.with(|c| c.set(0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
/// The custom allocator that handles the checking.
|
||||
///
|
||||
/// To use this crate, you must add the following in your `main.rs`:
|
||||
/// ```rust
|
||||
/// use assert_no_alloc::*;
|
||||
/// // ...
|
||||
/// #[cfg(debug_assertions)]
|
||||
/// #[global_allocator]
|
||||
/// static A: AllocDisabler = AllocDisabler;
|
||||
/// ```
|
||||
pub struct AllocDisabler;
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
impl AllocDisabler {
|
||||
fn check(&self, layout: Layout) {
|
||||
let forbid_count = ALLOC_FORBID_COUNT.with(|f| f.get());
|
||||
let permit_count = ALLOC_PERMIT_COUNT.with(|p| p.get());
|
||||
if forbid_count > 0 && permit_count == 0 {
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
ALLOC_VIOLATION_COUNT.with(|c| c.set(c.get()+1));
|
||||
|
||||
#[cfg(any( all(not(feature="warn_debug"), debug_assertions), all(not(feature="warn_release"), not(debug_assertions)) ))] // if abort mode is selected
|
||||
{
|
||||
#[cfg(all(feature = "log", feature = "backtrace"))]
|
||||
permit_alloc(|| log::error!("Memory allocation of {} bytes failed from:\n{:?}", layout.size(), backtrace::Backtrace::new()));
|
||||
#[cfg(all(feature = "log", not(feature = "backtrace")))]
|
||||
permit_alloc(|| log::error!("Memory allocation of {} bytes failed", layout.size()));
|
||||
|
||||
#[cfg(all(not(feature = "log"), feature = "backtrace"))]
|
||||
permit_alloc(|| eprintln!("Allocation failure from:\n{:?}", backtrace::Backtrace::new()));
|
||||
|
||||
// This handler can be overridden (although as of writing, the API to do so is still
|
||||
// unstable) so we must always call this even when the log feature is enabled
|
||||
std::alloc::handle_alloc_error(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
unsafe impl GlobalAlloc for AllocDisabler {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
self.check(layout);
|
||||
System.alloc(layout)
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
self.check(layout);
|
||||
System.dealloc(ptr, layout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for objects whose Drop implementation shall be permitted
|
||||
/// to (de)allocate.
|
||||
///
|
||||
/// Typical usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// let foo = PermitDrop::new(
|
||||
/// permit_alloc(||
|
||||
/// Box::new(...)
|
||||
/// )
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Here, creation of the Box is guarded by the explicit `permit_alloc` call,
|
||||
/// and destruction of the Box is guarded by PermitDrop. Neither creation nor
|
||||
/// destruction will cause an assertion failure from within `assert_no_alloc`.
|
||||
pub struct PermitDrop<T>(Option<T>);
|
||||
|
||||
impl<T> PermitDrop<T> {
|
||||
pub fn new(t: T) -> PermitDrop<T> {
|
||||
permit_alloc(|| {
|
||||
PermitDrop(Some(t))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for PermitDrop<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T { self.0.as_ref().unwrap() }
|
||||
}
|
||||
|
||||
impl<T> std::ops::DerefMut for PermitDrop<T> {
|
||||
fn deref_mut(&mut self) -> &mut T { self.0.as_mut().unwrap() }
|
||||
}
|
||||
|
||||
impl<I: Iterator> Iterator for PermitDrop<I> {
|
||||
type Item = I::Item;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
(**self).next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T> Drop for PermitDrop<T> {
|
||||
fn drop(&mut self) {
|
||||
let mut tmp = None;
|
||||
std::mem::swap(&mut tmp, &mut self.0);
|
||||
permit_alloc(|| {
|
||||
std::mem::drop(tmp);
|
||||
});
|
||||
}
|
||||
}
|
148
rust/rust-assert-no-alloc/tests/test.rs
Normal file
148
rust/rust-assert-no-alloc/tests/test.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use assert_no_alloc::*;
|
||||
use std::panic::catch_unwind;
|
||||
|
||||
#[global_allocator]
|
||||
static A: AllocDisabler = AllocDisabler;
|
||||
|
||||
#[cfg(not(feature = "warn_debug"))]
|
||||
compile_error!("The test suite requires the warn_debug feature to be enabled. Use `cargo test --features warn_debug`");
|
||||
|
||||
// This is only a kludge; what we actually want to check is "will do_alloc() be optimized out?", e.g. due to
|
||||
// compiler optimizations turned on in --release mode. We can't do that, the closest we can get is to check
|
||||
// whether debug_assertions are disabled, which coincidentially also happens in release mode.
|
||||
#[cfg(not(debug_assertions))]
|
||||
compile_error!("The test suite only works in debug mode. Use `cargo test --features warn_debug`");
|
||||
|
||||
#[cfg(feature = "warn_debug")]
|
||||
fn check_and_reset() -> bool {
|
||||
let result = violation_count() > 0;
|
||||
reset_violation_count();
|
||||
result
|
||||
}
|
||||
|
||||
// Provide a stub check_and_reset() function if warn_debug is disabled. This will never be compiled due to the
|
||||
// compile_error!() above, but this stub ensures that the output will not be cluttered with spurious error
|
||||
// messages.
|
||||
#[cfg(not(feature = "warn_debug"))]
|
||||
fn check_and_reset() -> bool { unreachable!() }
|
||||
|
||||
fn do_alloc() {
|
||||
let _tmp: Box<u32> = Box::new(42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_noop() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_simple() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
});
|
||||
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
});
|
||||
});
|
||||
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_simple() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
do_alloc();
|
||||
});
|
||||
assert_eq!(check_and_reset(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_in_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
do_alloc();
|
||||
});
|
||||
});
|
||||
assert_eq!(check_and_reset(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_after_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
});
|
||||
do_alloc();
|
||||
});
|
||||
assert_eq!(check_and_reset(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwind_ok() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
let r = catch_unwind(|| {
|
||||
assert_no_alloc(|| {
|
||||
panic!();
|
||||
});
|
||||
});
|
||||
assert!(r.is_err());
|
||||
});
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwind_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
let r = catch_unwind(|| {
|
||||
assert_no_alloc(|| {
|
||||
panic!();
|
||||
});
|
||||
});
|
||||
assert!(r.is_err());
|
||||
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), true);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwind_nested2() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
let r = catch_unwind(|| {
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
panic!();
|
||||
});
|
||||
});
|
||||
});
|
||||
assert!(r.is_err());
|
||||
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), true);
|
||||
});
|
||||
});
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
Loading…
Reference in New Issue
Block a user