pw_assert/pw_assert.rs
1// Copyright 2025 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14#![no_std]
15
16//! # pw_assert
17//!
18//! `pw_assert` provides crash-safe assert and panic macros that route to a
19//! central handler, [`pw_assert_HandleFailure`]. This is designed for embedded
20//! systems where standard library panics might not be suitable, or where
21//! specific logging/recovery behavior is needed.
22//!
23//! The macros in this crate are designed to be drop-in replacements for
24//! `core::panic!`, `core::assert!`, etc., but they route through `pw_log` to
25//! log the failure before calling the crash handler.
26//!
27//! # Example
28//!
29//! ```no_run
30//! #[unsafe(no_mangle)]
31//! #[allow(non_snake_case)]
32//! pub extern "C" fn pw_assert_HandleFailure() -> ! {
33//! pw_log::error!("Panicking!");
34//! loop {}
35//! }
36//! ```
37//!
38//! ```no_run
39//! pw_assert::assert!(42 == 16, "Stack start is not aligned");
40//!
41//! pw_assert::panic!("Unhandled interrupt: irq={}", 16 as u32);
42//!
43//! pw_assert::debug_assert!(1 == 0);
44//!
45//! pw_assert::debug_panic!("Next monotonic tick overflow");
46//! ```
47
48#[cfg(not(feature = "default_handler"))]
49unsafe extern "C" {
50 /// The crash handler called by asserts and panics when they fail.
51 ///
52 /// Since the `default_handler` feature is disabled, the application must
53 /// provide an implementation of this function with the `#[no_mangle]` attribute.
54 ///
55 /// This function must not return.
56 pub fn pw_assert_HandleFailure() -> !;
57}
58
59#[cfg(feature = "default_handler")]
60#[allow(non_snake_case)]
61/// Default implementation of the crash handler.
62///
63/// This implementation simply delegates to `core::panic!`.
64///
65/// # Safety
66///
67/// The default_handler panic handler is safe, but the unsafe keyword is
68/// required to match the non-default panic handler signature.
69pub unsafe extern "C-unwind" fn pw_assert_HandleFailure() -> ! {
70 core::panic!("pw_assert panic")
71}
72
73// Re-export pw_log for use by panic/assert macros.
74#[doc(hidden)]
75pub mod __private {
76 pub use pw_log::fatal;
77}
78
79#[cfg(feature = "color")]
80#[macro_export]
81macro_rules! __private_log_panic_banner {
82 () => {
83 // Colorized using https://glitchassassin.github.io/fk-ascii-editor/ and
84 // and run throught the following shell command to translate outputs:
85 // sed 's/{70}/\\x1b[0m/'g |
86 // sed 's/{B0}/\\x1b[1;33m/'g |
87 // sed 's/{90}/\\x1b[1;31m/'g |
88 // sed 's/{E0}/\\x1b[1;36m/'g |
89 // sed 's/{C0}/\\x1b[1;34m/'g |
90 // sed 's/{A0}/\\x1b[1;32m/'g |
91 // sed 's/{F0}/\\x1b[1;37m/'g |
92 // sed 's/{D0}/\\x1b[1;35m/'g
93 $crate::__private::fatal!("
94
95\x1b[0m.-------. ____ ,---. .--.\x1b[1;31m.-./`)\x1b[0m _______
96\x1b[0m\\ \x1b[1;33m_(`)_\x1b[0m \\ .' __ `. | \\ | |\x1b[1;31m\\\x1b[0m \x1b[1;36m.-.\x1b[1;31m')\x1b[0m / __ \\
97\x1b[0m| \x1b[1;33m(_\x1b[0m \x1b[1;32mo\x1b[1;33m._)\x1b[0m|/ ' \\ \\| , \\ | |\x1b[1;31m/\x1b[0m \x1b[1;36m`-'\x1b[0m \x1b[1;31m\\\x1b[0m | \x1b[1;36m,_\x1b[0m/ \\__)
98\x1b[0m| \x1b[1;33m(_,_)\x1b[0m /|___| / || |\\\x1b[1;34m_\x1b[0m \\| | \x1b[1;31m`-'`\"`\x1b[1;36m,-./ )
99\x1b[0m| '-.-' _.-` || \x1b[1;34m_( )_\x1b[0m\\ | .---. \x1b[1;36m\\ '\x1b[1;35m_\x1b[0m \x1b[1;36m'`)
100\x1b[0m| | .' \x1b[1;32m_\x1b[0m || \x1b[1;34m(_\x1b[0m \x1b[1;35mo\x1b[0m \x1b[1;34m_)\x1b[0m | | | \x1b[1;36m>\x1b[0m \x1b[1;35m(_)\x1b[0m \x1b[1;36m)\x1b[0m __
101\x1b[0m| | | \x1b[1;32m_( )_\x1b[0m || \x1b[1;34m(_,_)\x1b[0m\\ | | | \x1b[1;36m( . .-'\x1b[0m_/ )
102\x1b[0m/ ) \\ \x1b[1;32m(_\x1b[0m \x1b[1;31mo\x1b[0m \x1b[1;32m_)\x1b[0m /| | | | | | \x1b[1;36m`-'`-'\x1b[0m /
103\x1b[0m`---' '.\x1b[1;32m(_,_)\x1b[0m.' '--' '--' '---' `._____.'
104\x1b[0m
105")
106 };
107}
108
109#[cfg(not(feature = "color"))]
110#[macro_export]
111macro_rules! __private_log_panic_banner {
112 () => {
113 $crate::__private::fatal!(
114 r#"
115
116.-------. ____ ,---. .--..-./`) _______
117\ _(`)_ \ .' __ `. | \ | |\ .-.') / __ \
118| (_ o._)|/ ' \ \| , \ | |/ `-' \ | ,_/ \__)
119| (_,_) /|___| / || |\_ \| | `-'`"`,-./ )
120| '-.-' _.-` || _( )_\ | .---. \ '_ '`)
121| | .' _ || (_ o _) | | | > (_) ) __
122| | | _( )_ || (_,_)\ | | | ( . .-'_/ )
123/ ) \ (_ o _) /| | | | | | `-'`-' /
124`---' '.(_,_).' '--' '--' '---' `._____.'
125"#
126 )
127 };
128}
129
130/// Panics unconditionally.
131///
132/// This macro logs a panic banner and the formatted message at `FATAL` level
133/// using `pw_log`, and then calls the crash handler [`pw_assert_HandleFailure`].
134///
135/// # Examples
136///
137/// ```no_run
138/// pw_assert::panic!("Something went terribly wrong!");
139///
140/// pw_assert::panic!("Error code: {}", 42 as i32);
141/// ```
142#[macro_export]
143macro_rules! panic {
144 ($format_string:literal $(,)?) => {{
145 // Ideally we'd combine these two log statements. However, the `pw_log` API
146 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
147 $crate::__private_log_panic_banner!();
148 $crate::__private::fatal!($format_string);
149 unsafe{$crate::pw_assert_HandleFailure()}
150 }};
151
152 ($format_string:literal, $($args:expr),* $(,)?) => {{
153 // Ideally we'd combine these two log statements. However, the `pw_log` API
154 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
155 $crate::__private_log_panic_banner!();
156 $crate::__private::fatal!($format_string, $($args),*);
157 unsafe{$crate::pw_assert_HandleFailure()}
158 }};
159}
160
161/// Panics unconditionally when debug_assertions are enabled
162///
163/// If `debug_assertions` are enabled, this behaves exactly like [`panic!`].
164/// If `debug_assertions` are disabled, this macro is a no-op.
165///
166/// `debug_assertions` can be enabled by setting this bazel label to `True`
167/// `@pigweed//pw_assert/rust:debug_assertions`.
168///
169/// # Examples
170///
171/// ```no_run
172/// pw_assert::debug_panic!("This should never happen in debug mode.");
173/// ```
174#[macro_export]
175#[cfg(feature = "debug_assertions")]
176macro_rules! debug_panic {
177 ($format_string:literal $(,)?) => {{
178 // Ideally we'd combine these two log statements. However, the `pw_log` API
179 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
180 $crate::__private_log_panic_banner!();
181 $crate::__private::fatal!($format_string);
182 unsafe{$crate::pw_assert_HandleFailure()}
183 }};
184
185 ($format_string:literal, $($args:expr),* $(,)?) => {{
186 // Ideally we'd combine these two log statements. However, the `pw_log` API
187 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
188 $crate::__private_log_panic_banner!();
189 $crate::__private::fatal!($format_string, $($args),*);
190 unsafe{$crate::pw_assert_HandleFailure()}
191 }};
192}
193
194/// Panics unconditionally when debug_assertions are enabled.
195#[macro_export]
196#[cfg(not(feature = "debug_assertions"))]
197macro_rules! debug_panic {
198 ($format_string:literal $(,)?) => {{}};
199 ($format_string:literal, $($args:expr),* $(,)?) => {{}};
200}
201
202/// Asserts that a condition is true.
203///
204/// If the condition evaluates to `false`, this macro logs a panic banner,
205/// logs the failure (including line number and optional custom message) at
206/// `FATAL` level using `pw_log`, and then calls [`pw_assert_HandleFailure`].
207///
208/// # Examples
209///
210/// ```no_run
211/// let x = 5;
212/// pw_assert::assert!(x > 0);
213/// pw_assert::assert!(x == 5, "x should be 5, but was {}", x as i32);
214/// ```
215#[macro_export]
216macro_rules! assert {
217 ($condition:expr $(,)?) => {{
218 #[allow(clippy::unnecessary_cast)]
219 if !$condition {
220 // Ideally we'd combine these two log statements. However, the `pw_log` API
221 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
222 $crate::__private_log_panic_banner!();
223 $crate::__private::fatal!("assert!() failed @{}", line!() as u32);
224 unsafe{$crate::pw_assert_HandleFailure()}
225 }
226 }};
227
228 ($condition:expr, $($args:expr),* $(,)?) => {{
229 #[allow(clippy::unnecessary_cast)]
230 if !$condition {
231 // Ideally we'd combine these two log statements. However, the `pw_log` API
232 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
233 $crate::__private_log_panic_banner!();
234 $crate::__private::fatal!("assert!() failed");
235 $crate::__private::fatal!($($args),*);
236 unsafe{$crate::pw_assert_HandleFailure()}
237 }
238 }};
239}
240
241/// Asserts that a condition is true when debug_assertions are enabled.
242///
243/// If `debug_assertions` are enabled, this behaves exactly like [`assert!`].
244/// If `debug_assertions` are disabled, this macro is a no-op.
245///
246/// `debug_assertions` can be enabled by setting this bazel label to `True`
247/// `@pigweed//pw_assert/rust:debug_assertions`.
248///
249/// # Examples
250///
251/// ```no_run
252/// let x = 5;
253/// pw_assert::debug_assert!(x == 5);
254/// ```
255#[macro_export]
256#[cfg(feature = "debug_assertions")]
257macro_rules! debug_assert {
258 ($condition:expr $(,)?) => {
259 #[allow(clippy::unnecessary_cast)]
260 if !$condition {
261 // Ideally we'd combine these two log statements. However, the `pw_log` API
262 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
263 $crate::__private_log_panic_banner!();
264 $crate::__private::fatal!("debug_assert!() failed on line {}", line!() as u32);
265 unsafe{$crate::pw_assert_HandleFailure()}
266 }
267 };
268
269 ($condition:expr, $($args:expr),* $(,)?) => {
270 #[allow(clippy::unnecessary_cast)]
271 if !$condition {
272 // Ideally we'd combine these two log statements. However, the `pw_log` API
273 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
274 $crate::__private_log_panic_banner!();
275 $crate::__private::fatal!("debug_assert!() failed");
276 $crate::__private::fatal!($($args),*);
277 unsafe{$crate::pw_assert_HandleFailure()}
278 }
279 };
280}
281
282/// Asserts that a condition is true when debug_assertions are enabled.
283#[macro_export]
284#[cfg(not(feature = "debug_assertions"))]
285macro_rules! debug_assert {
286 ($condition:expr $(,)?) => {};
287 ($condition:expr, $($args:expr),* $(,)?) => {};
288}
289
290/// Asserts that two expressions are equal (equivalent to `assert_eq!`).
291///
292/// If the expressions are not equal, this macro logs a panic banner, logs the
293/// failure (including the values of the expressions and optional custom
294/// message) at `FATAL` level using `pw_log`, and then calls
295/// [`pw_assert_HandleFailure`].
296///
297/// Note that due to `pw_log` requirements, both expressions must be cast
298/// expressions (e.g., `x as i32`).
299///
300/// # Examples
301///
302/// ```no_run
303/// let x = 5;
304/// pw_assert::eq!(x as i32, 5 as i32);
305/// pw_assert::eq!(x as i32, 5 as i32, "x should be 5");
306/// ```
307#[macro_export]
308macro_rules! eq {
309 ($condition_a:expr, $condition_b:expr $(,)?) => {{
310 #[allow(clippy::deref_addrof)]
311 #[allow(clippy::unnecessary_cast)]
312 if *&$condition_a != *&$condition_b {
313 // Ideally we'd combine these two log statements. However, the `pw_log` API
314 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
315 $crate::__private_log_panic_banner!();
316 $crate::__private::fatal!("assert_eq!() failed, {} != {}", $condition_a, $condition_b);
317 unsafe{$crate::pw_assert_HandleFailure()}
318 }
319 }};
320
321 ($condition_a:expr, $condition_b:expr, $($args:expr),* $(,)?) => {{
322 #[allow(clippy::deref_addrof)]
323 #[allow(clippy::unnecessary_cast)]
324 if *&$condition_a != *&$condition_b {
325 // Ideally we'd combine these two log statements. However, the `pw_log` API
326 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
327 $crate::__private_log_panic_banner!();
328 $crate::__private::fatal!("assert_eq!() failed, {} != {}", $condition_a, $condition_b);
329 $crate::__private::fatal!($($args),*);
330 unsafe{$crate::pw_assert_HandleFailure()}
331 }
332 }};
333}
334
335/// Asserts that two expressions are not equal (equivalent to `assert_ne!`).
336///
337/// If the expressions are equal, this macro logs a panic banner, logs the
338/// failure (including the values of the expressions and optional custom
339/// message) at `FATAL` level using `pw_log`, and then calls
340/// [`pw_assert_HandleFailure`].
341///
342/// Note that due to `pw_log` requirements, both expressions must be cast
343/// expressions (e.g., `x as i32`).
344///
345/// # Examples
346///
347/// ```no_run
348/// let x = 5;
349/// pw_assert::ne!(x as i32, 6 as i32);
350/// pw_assert::ne!(x as i32, 6 as i32, "x should not be 6");
351/// ```
352#[macro_export]
353macro_rules! ne {
354 ($condition_a:expr, $condition_b:expr $(,)?) => {{
355 #[allow(clippy::deref_addrof)]
356 #[allow(clippy::unnecessary_cast)]
357 if *&$condition_a == *&$condition_b {
358 // Ideally we'd combine these two log statements. However, the `pw_log` API
359 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
360 $crate::__private_log_panic_banner!();
361 $crate::__private::fatal!("assert_neq!() failed, {} == {}", $condition_a, $condition_b);
362 unsafe{$crate::pw_assert_HandleFailure()}
363 }
364 }};
365
366 ($condition_a:expr, $condition_b:expr, $($args:expr),* $(,)?) => {{
367 #[allow(clippy::deref_addrof)]
368 #[allow(clippy::unnecessary_cast)]
369 if *&$condition_a == *&$condition_b {
370 // Ideally we'd combine these two log statements. However, the `pw_log` API
371 // does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
372 $crate::__private_log_panic_banner!();
373 $crate::__private::fatal!("assert_neq!() failed, {} == {}", $condition_a, $condition_b);
374 $crate::__private::fatal!($($args),*);
375 unsafe{$crate::pw_assert_HandleFailure()}
376 }
377 }};
378}
379
380#[cfg(test)]
381mod tests {
382 use unittest::test;
383
384 // Because infrastructure to verify panics does not exist, these tests only
385 // check for the valid condition and the syntax of the macros being correct.
386
387 #[test]
388 fn assert_syntax_works() -> unittest::Result<()> {
389 assert!(true as bool);
390 assert!(true as bool,);
391
392 assert!(true as bool, "custom msg");
393 assert!(true as bool, "custom msg",);
394
395 assert!(true as bool, "custom msg with arg {}", 42 as u32);
396 assert!(true as bool, "custom msg with arg {}", 42 as u32,);
397
398 Ok(())
399 }
400
401 #[test]
402 fn debug_assert_syntax_works() -> unittest::Result<()> {
403 debug_assert!(true as bool);
404 debug_assert!(true as bool,);
405
406 debug_assert!(true as bool, "custom msg");
407 debug_assert!(true as bool, "custom msg",);
408
409 debug_assert!(true as bool, "custom msg with arg {}", 42 as u32);
410 debug_assert!(true as bool, "custom msg with arg {}", 42 as u32,);
411
412 Ok(())
413 }
414
415 #[test]
416 fn assert_eq_syntax_works() -> unittest::Result<()> {
417 eq!(1 as u32, 1 as u32);
418 eq!(1 as u32, 1 as u32,);
419
420 eq!(1 as u32, 1 as u32, "custom msg");
421 eq!(1 as u32, 1 as u32, "custom msg",);
422
423 eq!(1 as u32, 1 as u32, "custom msg with arg {}", 42 as u32);
424 eq!(1 as u32, 1 as u32, "custom msg with arg {}", 42 as u32,);
425
426 Ok(())
427 }
428
429 #[test]
430 fn assert_ne_syntax_works() -> unittest::Result<()> {
431 ne!(1 as u32, 2 as u32);
432 ne!(1 as u32, 2 as u32,);
433
434 ne!(1 as u32, 2 as u32, "custom msg");
435 ne!(1 as u32, 2 as u32, "custom msg",);
436
437 ne!(1 as u32, 2 as u32, "custom msg with arg {}", 42 as u32);
438 ne!(1 as u32, 2 as u32, "custom msg with arg {}", 42 as u32,);
439
440 Ok(())
441 }
442}