Browse Source

Renaming and a new macro

Bradlee Speice 5 months ago
parent
commit
65673e1af2
8 changed files with 164 additions and 63 deletions
  1. 47
    9
      README.md
  2. 2
    2
      examples/release_mode.rs
  3. 0
    20
      examples/setup_warning.rs
  4. 1
    1
      qadapt-macro/src/lib.rs
  5. 70
    20
      src/lib.rs
  6. 26
    0
      tests/assert_macro.rs
  7. 11
    11
      tests/proc_macro.rs
  8. 7
    0
      tests/unused_panic.rs

+ 47
- 9
README.md View File

@@ -8,15 +8,53 @@
8 8
 
9 9
 
10 10
 ---
11
-# The Quick And Dirty Allocation Profiling Tool
11
+# QADAPT - `debug_assert!` for your memory
12 12
 
13
-This allocator is a helper for writing high-performance code that is allocation/drop free;
14
-for functions annotated with `#[allocate_panic]`, QADAPT will detect when allocations/drops
15
-happen during their execution (and execution of any functions they call) and throw a
16
-thread panic if this occurs. QADAPT-related code is *stripped out during release builds*,
17
-so no worries about random allocations crashing in production.
13
+This allocator is a helper for writing high-performance code that is memory-sensitive;
14
+a thread panic will be triggered if a function annotated with `#[no_alloc]`,
15
+or code inside an `assert_no_alloc!` macro interacts with the allocator in any way.
16
+Wanton allocations and unforeseen drops no more - this library lets you focus on
17
+writing code without worrying if Rust properly managed to inline the variable into the stack.
18 18
 
19
-Currently this crate is Nightly-only, but will work once `const fn` is in Stable.
19
+Now, an allocator blowing up in production is a scary thought; that's why QADAPT
20
+is designed to strip its own code out whenever you're running with a release build.
21
+Just like the [`debug_assert!` macro](https://doc.rust-lang.org/std/macro.debug_assert.html)
22
+in Rust's standard library, it's safe to use without worrying about a unforeseen
23
+circumstance causing your application to crash.
20 24
 
21
-Please also take a look at [qadapt-macro](https://github.com/bspeice/qadapt/tree/master/qadapt-macro)
22
-for some helper macros to make working with QADAPT a bit easier.
25
+# Usage
26
+
27
+Actually making use of QADAPT is straight-forward. To set up the allocator,
28
+place the following snippet in either your program binaries (main.rs) or tests:
29
+
30
+```rust,ignore
31
+use qadapt::QADAPT;
32
+
33
+#[global_allocator]
34
+static Q: QADAPT = QADAPT;
35
+```
36
+
37
+After that, there are two ways of telling QADAPT that it should trigger a panic:
38
+
39
+1. Annotate functions with the `#[no_alloc]` proc macro:
40
+```rust,no_run
41
+use qadapt::no_alloc;
42
+
43
+#[no_alloc]
44
+fn do_math() -> u8 {
45
+2 + 2
46
+}
47
+```
48
+
49
+2. Evaluate expressions with the `assert_no_alloc!` macro
50
+```rust,no_run
51
+use qadapt::assert_no_alloc;
52
+
53
+fn do_work() {
54
+// This code is allowed to trigger an allocation
55
+let b = Box::new(8);
56
+
57
+// This code would panic if an allocation occurred inside it
58
+let x = assert_no_alloc!(*b + 2);
59
+assert_eq!(x, 10);
60
+}

+ 2
- 2
examples/release_mode.rs View File

@@ -1,10 +1,10 @@
1
-use qadapt::allocate_panic;
1
+use qadapt::no_alloc;
2 2
 use qadapt::QADAPT;
3 3
 
4 4
 #[global_allocator]
5 5
 static Q: QADAPT = QADAPT;
6 6
 
7
-#[allocate_panic]
7
+#[no_alloc]
8 8
 fn does_allocate() -> Box<u8> {
9 9
     Box::new(0)
10 10
 }

+ 0
- 20
examples/setup_warning.rs View File

@@ -1,20 +0,0 @@
1
-use env_logger;
2
-
3
-use qadapt::allocate_panic;
4
-
5
-// Note that we're missing the `#[global_allocator]` attribute
6
-
7
-#[allocate_panic]
8
-fn does_allocate() -> Box<u8> {
9
-    Box::new(0)
10
-}
11
-
12
-fn main() {
13
-    // This code will warn that QADAPT isn't being used, but won't trigger a panic.
14
-    // Run with `RUST_LOG=warn cargo run --example setup_warning`
15
-    env_logger::init();
16
-    does_allocate();
17
-
18
-    // The warning will only trigger once though
19
-    does_allocate();
20
-}

+ 1
- 1
qadapt-macro/src/lib.rs View File

@@ -155,7 +155,7 @@ fn escape_return(ts: TokenStream) -> TokenStream {
155 155
 /// separate thread, or defers allocations via closure/Future, those results
156 156
 /// will not trigger an error.
157 157
 #[proc_macro_attribute]
158
-pub fn allocate_panic(_attr: TokenStream, item: TokenStream) -> TokenStream {
158
+pub fn no_alloc(_attr: TokenStream, item: TokenStream) -> TokenStream {
159 159
     let mut protected_fn: Vec<TokenTree> = Vec::new();
160 160
     let mut item_iter = item.into_iter();
161 161
 

+ 70
- 20
src/lib.rs View File

@@ -1,15 +1,53 @@
1
-//! # The Quick And Dirty Allocation Profiling Tool
1
+//! # QADAPT - `debug_assert!` for your memory
2 2
 //!
3
-//! This allocator is a helper for writing high-performance code that is allocation/drop free;
4
-//! for functions annotated with `#[allocate_panic]`, QADAPT will detect when allocations/drops
5
-//! happen during their execution (and execution of any functions they call) and throw a
6
-//! thread panic if this occurs. QADAPT-related code is *stripped out during release builds*,
7
-//! so no worries about random allocations crashing in production.
8
-//!
9
-//! Currently this crate is Nightly-only, but will work once `const fn` is in Stable.
10
-//!
11
-//! Please also take a look at [qadapt-macro](https://github.com/bspeice/qadapt/tree/master/qadapt-macro)
12
-//! for some helper macros to make working with QADAPT a bit easier.
3
+//! This allocator is a helper for writing high-performance code that is memory-sensitive;
4
+//! a thread panic will be triggered if a function annotated with `#[no_alloc]`,
5
+//! or code inside an `assert_no_alloc!` macro interacts with the allocator in any way.
6
+//! Wanton allocations and unforeseen drops no more - this library lets you focus on
7
+//! writing code without worrying if Rust properly managed to inline the variable into the stack.
8
+//! 
9
+//! Now, an allocator blowing up in production is a scary thought; that's why QADAPT
10
+//! is designed to strip its own code out whenever you're running with a release build.
11
+//! Just like the [`debug_assert!` macro](https://doc.rust-lang.org/std/macro.debug_assert.html)
12
+//! in Rust's standard library, it's safe to use without worrying about a unforeseen
13
+//! circumstance causing your application to crash.
14
+//! 
15
+//! # Usage
16
+//! 
17
+//! Actually making use of QADAPT is straight-forward. To set up the allocator,
18
+//! place the following snippet in either your program binaries (main.rs) or tests:
19
+//! 
20
+//! ```rust,ignore
21
+//! use qadapt::QADAPT;
22
+//! 
23
+//! #[global_allocator]
24
+//! static Q: QADAPT = QADAPT;
25
+//! ```
26
+//! 
27
+//! After that, there are two ways of telling QADAPT that it should trigger a panic:
28
+//! 
29
+//! 1. Annotate functions with the `#[no_alloc]` proc macro:
30
+//! ```rust,no_run
31
+//! use qadapt::no_alloc;
32
+//! 
33
+//! #[no_alloc]
34
+//! fn do_math() -> u8 {
35
+//!     2 + 2
36
+//! }
37
+//! ```
38
+//! 
39
+//! 2. Evaluate expressions with the `assert_no_alloc!` macro
40
+//! ```rust,no_run
41
+//! use qadapt::assert_no_alloc;
42
+//! 
43
+//! fn do_work() {
44
+//!     // This code is allowed to trigger an allocation
45
+//!     let b = Box::new(8);
46
+//!     
47
+//!     // This code would panic if an allocation occurred inside it
48
+//!     let x = assert_no_alloc!(*b + 2);
49
+//!     assert_eq!(x, 10);
50
+//! }
13 51
 #![deny(missing_docs)]
14 52
 
15 53
 use log::warn;
@@ -45,8 +83,7 @@ pub fn enter_protected() {
45 83
         }
46 84
 
47 85
         if !*IS_ACTIVE.read() {
48
-            *IS_ACTIVE.write() = true;
49
-            warn!("QADAPT not initialized when using allocation guards; please verify `#[global_allocator]` is set!");
86
+            panic!("QADAPT not initialized when using allocation guards; please verify `#[global_allocator]` is set!");
50 87
         }
51 88
 
52 89
         PROTECTION_LEVEL
@@ -80,17 +117,30 @@ pub fn exit_protected() {
80 117
     }
81 118
 }
82 119
 
120
+/// Get the result of an expression, guaranteeing that no allocations occur
121
+/// during its evaluation.
122
+/// 
123
+/// **Warning**: Unexpected behavior may occur when using the `return` keyword.
124
+/// Because the macro cleanup logic will not be run, QADAPT may trigger a panic
125
+/// in code that was not specifically intended to be allocation-free.
126
+#[macro_export]
127
+macro_rules! assert_no_alloc {
128
+    ($e:expr) => {{
129
+        ::qadapt::enter_protected();
130
+        let e = { $e };
131
+        ::qadapt::exit_protected();
132
+        e
133
+    }};
134
+}
135
+
83 136
 static IS_ACTIVE: RwLock<bool> = RwLock::new(false);
84 137
 static INTERNAL_ALLOCATION: RwLock<usize> = RwLock::new(usize::max_value());
85 138
 
86 139
 /// Get the current "protection level" in QADAPT: calls to enter_protected() - exit_protected()
87 140
 pub fn protection_level() -> usize {
88
-    #[cfg(debug_assertions)]
89
-    {
141
+    if cfg!(debug_assertions) {
90 142
         PROTECTION_LEVEL.try_with(|v| *v.read()).unwrap_or(0)
91
-    }
92
-    #[cfg(not(debug_assertions))]
93
-    {
143
+    } else {
94 144
         0
95 145
     }
96 146
 }
@@ -131,7 +181,7 @@ unsafe impl GlobalAlloc for QADAPT {
131 181
         }
132 182
 
133 183
         // Because accessing PROTECTION_LEVEL has the potential to trigger an allocation,
134
-        // we need to spin until we can claim the INTERNAL_ALLOCATION lock for our thread.
184
+        // we need to acquire the INTERNAL_ALLOCATION lock for our thread.
135 185
         claim_internal_alloc();
136 186
         let protection_level: Result<usize, ()> =
137 187
             PROTECTION_LEVEL.try_with(|v| *v.read()).or(Ok(0));
@@ -167,7 +217,7 @@ unsafe impl GlobalAlloc for QADAPT {
167 217
         free(ptr as *mut c_void);
168 218
         match protection_level {
169 219
             Ok(v) if v > 0 => {
170
-                // Tripped a bad dealloc, but make sure further memory access during unwind
220
+                // Tripped a bad drop, but make sure further memory access during unwind
171 221
                 // doesn't have issues
172 222
                 PROTECTION_LEVEL.with(|v| *v.write() = 0);
173 223
                 panic!(

+ 26
- 0
tests/assert_macro.rs View File

@@ -0,0 +1,26 @@
1
+use qadapt::assert_no_alloc;
2
+use qadapt::QADAPT;
3
+
4
+#[global_allocator]
5
+static Q: QADAPT = QADAPT;
6
+
7
+#[test]
8
+fn math() {
9
+    let x = assert_no_alloc!(2 + 2);
10
+    assert_eq!(x, 4);
11
+}
12
+
13
+fn early_return() -> usize {
14
+    assert_no_alloc!(return 8)
15
+}
16
+
17
+fn into_box() -> Box<usize> {
18
+    Box::new(early_return())
19
+}
20
+
21
+#[test]
22
+#[should_panic]
23
+fn early_return_boxing() {
24
+    into_box();
25
+}
26
+

tests/macros.rs → tests/proc_macro.rs View File

@@ -1,12 +1,12 @@
1 1
 use std::io;
2 2
 
3
-use qadapt::allocate_panic;
3
+use qadapt::no_alloc;
4 4
 use qadapt::QADAPT;
5 5
 
6 6
 #[global_allocator]
7 7
 static Q: QADAPT = QADAPT;
8 8
 
9
-#[allocate_panic]
9
+#[no_alloc]
10 10
 fn no_allocate() {
11 11
     let _v: Vec<()> = Vec::with_capacity(0);
12 12
 }
@@ -16,7 +16,7 @@ fn macro_no_allocate() {
16 16
     no_allocate();
17 17
 }
18 18
 
19
-#[allocate_panic]
19
+#[no_alloc]
20 20
 fn allocates() {
21 21
     assert_eq!(::qadapt::protection_level(), 1);
22 22
     // Without boxing, release profile can actually optimize out the allocation
@@ -30,7 +30,7 @@ fn macro_allocates() {
30 30
     allocates();
31 31
 }
32 32
 
33
-#[allocate_panic]
33
+#[no_alloc]
34 34
 fn no_allocate_ret() -> bool {
35 35
     return true;
36 36
 }
@@ -40,7 +40,7 @@ fn macro_return() {
40 40
     assert!(no_allocate_ret());
41 41
 }
42 42
 
43
-#[allocate_panic]
43
+#[no_alloc]
44 44
 fn no_allocate_implicit_ret() -> bool {
45 45
     true
46 46
 }
@@ -50,7 +50,7 @@ fn macro_implicit_return() {
50 50
     assert!(no_allocate_implicit_ret());
51 51
 }
52 52
 
53
-#[allocate_panic]
53
+#[no_alloc]
54 54
 fn no_allocate_arg(b: bool) -> bool {
55 55
     b
56 56
 }
@@ -61,7 +61,7 @@ fn macro_allocate_arg() {
61 61
     no_allocate_arg(false);
62 62
 }
63 63
 
64
-#[allocate_panic]
64
+#[no_alloc]
65 65
 fn no_allocate_args(_b: bool, _u: usize, i: i64) -> i64 {
66 66
     i
67 67
 }
@@ -72,7 +72,7 @@ fn macro_allocate_args() {
72 72
     no_allocate_args(false, 4, -90);
73 73
 }
74 74
 
75
-#[allocate_panic]
75
+#[no_alloc]
76 76
 fn return_result(r: Result<usize, io::Error>) -> Result<Result<usize, io::Error>, ()> {
77 77
     Ok(r)
78 78
 }
@@ -82,7 +82,7 @@ fn macro_return_result() {
82 82
     return_result(Ok(16)).unwrap().unwrap();
83 83
 }
84 84
 
85
-#[allocate_panic]
85
+#[no_alloc]
86 86
 fn branching_return(a: bool, b: bool, c: bool) -> u8 {
87 87
     if a {
88 88
         if b {
@@ -131,7 +131,7 @@ fn run_closure(x: impl Fn(bool, bool) -> bool) -> bool {
131 131
     x(true, false)
132 132
 }
133 133
 
134
-#[allocate_panic]
134
+#[no_alloc]
135 135
 fn example_closure() {
136 136
     let c = run_closure(|a: bool, b| return a && b);
137 137
     assert!(!c);
@@ -145,7 +145,7 @@ fn macro_closure() {
145 145
 }
146 146
 
147 147
 #[test]
148
-#[allocate_panic]
148
+#[no_alloc]
149 149
 fn macro_release_safe() {
150 150
     #[cfg(debug_assertions)]
151 151
     {

+ 7
- 0
tests/unused_panic.rs View File

@@ -0,0 +1,7 @@
1
+use qadapt::enter_protected;
2
+
3
+#[test]
4
+#[should_panic]
5
+fn guard_without_initialization() {
6
+    enter_protected();
7
+}

Loading…
Cancel
Save