Skip to main content

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}