blob: 8f5e182d9b116d8719d20418c2b1d4ff5b612af5 [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#![no_std]
//! # pw_assert
//!
//! `pw_assert` provides crash-safe assert and panic macros that route to a
//! central handler, [`pw_assert_HandleFailure`]. This is designed for embedded
//! systems where standard library panics might not be suitable, or where
//! specific logging/recovery behavior is needed.
//!
//! The macros in this crate are designed to be drop-in replacements for
//! `core::panic!`, `core::assert!`, etc., but they route through `pw_log` to
//! log the failure before calling the crash handler.
//!
//! # Example
//!
//! ```no_run
//! #[unsafe(no_mangle)]
//! #[allow(non_snake_case)]
//! pub extern "C" fn pw_assert_HandleFailure() -> ! {
//! pw_log::error!("Panicking!");
//! loop {}
//! }
//! ```
//!
//! ```no_run
//! pw_assert::assert!(42 == 16, "Stack start is not aligned");
//!
//! pw_assert::panic!("Unhandled interrupt: irq={}", 16 as u32);
//!
//! pw_assert::debug_assert!(1 == 0);
//!
//! pw_assert::debug_panic!("Next monotonic tick overflow");
//! ```
#[cfg(not(feature = "default_handler"))]
unsafe extern "C" {
/// The crash handler called by asserts and panics when they fail.
///
/// Since the `default_handler` feature is disabled, the application must
/// provide an implementation of this function with the `#[no_mangle]` attribute.
///
/// This function must not return.
pub fn pw_assert_HandleFailure() -> !;
}
#[cfg(feature = "default_handler")]
#[allow(non_snake_case)]
/// Default implementation of the crash handler.
///
/// This implementation simply delegates to `core::panic!`.
///
/// # Safety
///
/// The default_handler panic handler is safe, but the unsafe keyword is
/// required to match the non-default panic handler signature.
pub unsafe extern "C-unwind" fn pw_assert_HandleFailure() -> ! {
core::panic!("pw_assert panic")
}
// Re-export pw_log for use by panic/assert macros.
#[doc(hidden)]
pub mod __private {
pub use pw_log::fatal;
}
#[cfg(feature = "color")]
#[macro_export]
macro_rules! __private_log_panic_banner {
() => {
// Colorized using https://glitchassassin.github.io/fk-ascii-editor/ and
// and run throught the following shell command to translate outputs:
// sed 's/{70}/\\x1b[0m/'g |
// sed 's/{B0}/\\x1b[1;33m/'g |
// sed 's/{90}/\\x1b[1;31m/'g |
// sed 's/{E0}/\\x1b[1;36m/'g |
// sed 's/{C0}/\\x1b[1;34m/'g |
// sed 's/{A0}/\\x1b[1;32m/'g |
// sed 's/{F0}/\\x1b[1;37m/'g |
// sed 's/{D0}/\\x1b[1;35m/'g
$crate::__private::fatal!("
\x1b[0m.-------. ____ ,---. .--.\x1b[1;31m.-./`)\x1b[0m _______
\x1b[0m\\ \x1b[1;33m_(`)_\x1b[0m \\ .' __ `. | \\ | |\x1b[1;31m\\\x1b[0m \x1b[1;36m.-.\x1b[1;31m')\x1b[0m / __ \\
\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/ \\__)
\x1b[0m| \x1b[1;33m(_,_)\x1b[0m /|___| / || |\\\x1b[1;34m_\x1b[0m \\| | \x1b[1;31m`-'`\"`\x1b[1;36m,-./ )
\x1b[0m| '-.-' _.-` || \x1b[1;34m_( )_\x1b[0m\\ | .---. \x1b[1;36m\\ '\x1b[1;35m_\x1b[0m \x1b[1;36m'`)
\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 __
\x1b[0m| | | \x1b[1;32m_( )_\x1b[0m || \x1b[1;34m(_,_)\x1b[0m\\ | | | \x1b[1;36m( . .-'\x1b[0m_/ )
\x1b[0m/ ) \\ \x1b[1;32m(_\x1b[0m \x1b[1;31mo\x1b[0m \x1b[1;32m_)\x1b[0m /| | | | | | \x1b[1;36m`-'`-'\x1b[0m /
\x1b[0m`---' '.\x1b[1;32m(_,_)\x1b[0m.' '--' '--' '---' `._____.'
\x1b[0m
")
};
}
#[cfg(not(feature = "color"))]
#[macro_export]
macro_rules! __private_log_panic_banner {
() => {
$crate::__private::fatal!(
r#"
.-------. ____ ,---. .--..-./`) _______
\ _(`)_ \ .' __ `. | \ | |\ .-.') / __ \
| (_ o._)|/ ' \ \| , \ | |/ `-' \ | ,_/ \__)
| (_,_) /|___| / || |\_ \| | `-'`"`,-./ )
| '-.-' _.-` || _( )_\ | .---. \ '_ '`)
| | .' _ || (_ o _) | | | > (_) ) __
| | | _( )_ || (_,_)\ | | | ( . .-'_/ )
/ ) \ (_ o _) /| | | | | | `-'`-' /
`---' '.(_,_).' '--' '--' '---' `._____.'
"#
)
};
}
/// Panics unconditionally.
///
/// This macro logs a panic banner and the formatted message at `FATAL` level
/// using `pw_log`, and then calls the crash handler [`pw_assert_HandleFailure`].
///
/// # Examples
///
/// ```no_run
/// pw_assert::panic!("Something went terribly wrong!");
///
/// pw_assert::panic!("Error code: {}", 42 as i32);
/// ```
#[macro_export]
macro_rules! panic {
($format_string:literal $(,)?) => {{
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!($format_string);
unsafe{$crate::pw_assert_HandleFailure()}
}};
($format_string:literal, $($args:expr),* $(,)?) => {{
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!($format_string, $($args),*);
unsafe{$crate::pw_assert_HandleFailure()}
}};
}
/// Panics unconditionally when debug_assertions are enabled
///
/// If `debug_assertions` are enabled, this behaves exactly like [`panic!`].
/// If `debug_assertions` are disabled, this macro is a no-op.
///
/// `debug_assertions` can be enabled by setting this bazel label to `True`
/// `@pigweed//pw_assert/rust:debug_assertions`.
///
/// # Examples
///
/// ```no_run
/// pw_assert::debug_panic!("This should never happen in debug mode.");
/// ```
#[macro_export]
#[cfg(feature = "debug_assertions")]
macro_rules! debug_panic {
($format_string:literal $(,)?) => {{
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!($format_string);
unsafe{$crate::pw_assert_HandleFailure()}
}};
($format_string:literal, $($args:expr),* $(,)?) => {{
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!($format_string, $($args),*);
unsafe{$crate::pw_assert_HandleFailure()}
}};
}
/// Panics unconditionally when debug_assertions are enabled.
#[macro_export]
#[cfg(not(feature = "debug_assertions"))]
macro_rules! debug_panic {
($format_string:literal $(,)?) => {{}};
($format_string:literal, $($args:expr),* $(,)?) => {{}};
}
/// Asserts that a condition is true.
///
/// If the condition evaluates to `false`, this macro logs a panic banner,
/// logs the failure (including line number and optional custom message) at
/// `FATAL` level using `pw_log`, and then calls [`pw_assert_HandleFailure`].
///
/// # Examples
///
/// ```no_run
/// let x = 5;
/// pw_assert::assert!(x > 0);
/// pw_assert::assert!(x == 5, "x should be 5, but was {}", x as i32);
/// ```
#[macro_export]
macro_rules! assert {
($condition:expr $(,)?) => {{
#[allow(clippy::unnecessary_cast)]
if !$condition {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("assert!() failed @{}", line!() as u32);
unsafe{$crate::pw_assert_HandleFailure()}
}
}};
($condition:expr, $($args:expr),* $(,)?) => {{
#[allow(clippy::unnecessary_cast)]
if !$condition {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("assert!() failed");
$crate::__private::fatal!($($args),*);
unsafe{$crate::pw_assert_HandleFailure()}
}
}};
}
/// Asserts that a condition is true when debug_assertions are enabled.
///
/// If `debug_assertions` are enabled, this behaves exactly like [`assert!`].
/// If `debug_assertions` are disabled, this macro is a no-op.
///
/// `debug_assertions` can be enabled by setting this bazel label to `True`
/// `@pigweed//pw_assert/rust:debug_assertions`.
///
/// # Examples
///
/// ```no_run
/// let x = 5;
/// pw_assert::debug_assert!(x == 5);
/// ```
#[macro_export]
#[cfg(feature = "debug_assertions")]
macro_rules! debug_assert {
($condition:expr $(,)?) => {
#[allow(clippy::unnecessary_cast)]
if !$condition {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("debug_assert!() failed on line {}", line!() as u32);
unsafe{$crate::pw_assert_HandleFailure()}
}
};
($condition:expr, $($args:expr),* $(,)?) => {
#[allow(clippy::unnecessary_cast)]
if !$condition {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("debug_assert!() failed");
$crate::__private::fatal!($($args),*);
unsafe{$crate::pw_assert_HandleFailure()}
}
};
}
/// Asserts that a condition is true when debug_assertions are enabled.
#[macro_export]
#[cfg(not(feature = "debug_assertions"))]
macro_rules! debug_assert {
($condition:expr $(,)?) => {};
($condition:expr, $($args:expr),* $(,)?) => {};
}
/// Asserts that two expressions are equal (equivalent to `assert_eq!`).
///
/// If the expressions are not equal, this macro logs a panic banner, logs the
/// failure (including the values of the expressions and optional custom
/// message) at `FATAL` level using `pw_log`, and then calls
/// [`pw_assert_HandleFailure`].
///
/// Note that due to `pw_log` requirements, both expressions must be cast
/// expressions (e.g., `x as i32`).
///
/// # Examples
///
/// ```no_run
/// let x = 5;
/// pw_assert::eq!(x as i32, 5 as i32);
/// pw_assert::eq!(x as i32, 5 as i32, "x should be 5");
/// ```
#[macro_export]
macro_rules! eq {
($condition_a:expr, $condition_b:expr $(,)?) => {{
#[allow(clippy::deref_addrof)]
#[allow(clippy::unnecessary_cast)]
if *&$condition_a != *&$condition_b {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("assert_eq!() failed, {} != {}", $condition_a, $condition_b);
unsafe{$crate::pw_assert_HandleFailure()}
}
}};
($condition_a:expr, $condition_b:expr, $($args:expr),* $(,)?) => {{
#[allow(clippy::deref_addrof)]
#[allow(clippy::unnecessary_cast)]
if *&$condition_a != *&$condition_b {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("assert_eq!() failed, {} != {}", $condition_a, $condition_b);
$crate::__private::fatal!($($args),*);
unsafe{$crate::pw_assert_HandleFailure()}
}
}};
}
/// Asserts that two expressions are not equal (equivalent to `assert_ne!`).
///
/// If the expressions are equal, this macro logs a panic banner, logs the
/// failure (including the values of the expressions and optional custom
/// message) at `FATAL` level using `pw_log`, and then calls
/// [`pw_assert_HandleFailure`].
///
/// Note that due to `pw_log` requirements, both expressions must be cast
/// expressions (e.g., `x as i32`).
///
/// # Examples
///
/// ```no_run
/// let x = 5;
/// pw_assert::ne!(x as i32, 6 as i32);
/// pw_assert::ne!(x as i32, 6 as i32, "x should not be 6");
/// ```
#[macro_export]
macro_rules! ne {
($condition_a:expr, $condition_b:expr $(,)?) => {{
#[allow(clippy::deref_addrof)]
#[allow(clippy::unnecessary_cast)]
if *&$condition_a == *&$condition_b {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("assert_neq!() failed, {} == {}", $condition_a, $condition_b);
unsafe{$crate::pw_assert_HandleFailure()}
}
}};
($condition_a:expr, $condition_b:expr, $($args:expr),* $(,)?) => {{
#[allow(clippy::deref_addrof)]
#[allow(clippy::unnecessary_cast)]
if *&$condition_a == *&$condition_b {
// Ideally we'd combine these two log statements. However, the `pw_log` API
// does not support passing through `PW_FMT_CONCAT` tokens to `pw_format`.
$crate::__private_log_panic_banner!();
$crate::__private::fatal!("assert_neq!() failed, {} == {}", $condition_a, $condition_b);
$crate::__private::fatal!($($args),*);
unsafe{$crate::pw_assert_HandleFailure()}
}
}};
}
#[cfg(test)]
mod tests {
use unittest::test;
// Because infrastructure to verify panics does not exist, these tests only
// check for the valid condition and the syntax of the macros being correct.
#[test]
fn assert_syntax_works() -> unittest::Result<()> {
assert!(true as bool);
assert!(true as bool,);
assert!(true as bool, "custom msg");
assert!(true as bool, "custom msg",);
assert!(true as bool, "custom msg with arg {}", 42 as u32);
assert!(true as bool, "custom msg with arg {}", 42 as u32,);
Ok(())
}
#[test]
fn debug_assert_syntax_works() -> unittest::Result<()> {
debug_assert!(true as bool);
debug_assert!(true as bool,);
debug_assert!(true as bool, "custom msg");
debug_assert!(true as bool, "custom msg",);
debug_assert!(true as bool, "custom msg with arg {}", 42 as u32);
debug_assert!(true as bool, "custom msg with arg {}", 42 as u32,);
Ok(())
}
#[test]
fn assert_eq_syntax_works() -> unittest::Result<()> {
eq!(1 as u32, 1 as u32);
eq!(1 as u32, 1 as u32,);
eq!(1 as u32, 1 as u32, "custom msg");
eq!(1 as u32, 1 as u32, "custom msg",);
eq!(1 as u32, 1 as u32, "custom msg with arg {}", 42 as u32);
eq!(1 as u32, 1 as u32, "custom msg with arg {}", 42 as u32,);
Ok(())
}
#[test]
fn assert_ne_syntax_works() -> unittest::Result<()> {
ne!(1 as u32, 2 as u32);
ne!(1 as u32, 2 as u32,);
ne!(1 as u32, 2 as u32, "custom msg");
ne!(1 as u32, 2 as u32, "custom msg",);
ne!(1 as u32, 2 as u32, "custom msg with arg {}", 42 as u32);
ne!(1 as u32, 2 as u32, "custom msg with arg {}", 42 as u32,);
Ok(())
}
}