1use once_cell::sync::Lazy;
6
7use crate::in_test_configuration;
8
9#[macro_export]
10macro_rules! fatal {
11 ($($arg:tt)*) => {{
12 tracing::error!(fatal = true, $($arg)*);
13 panic!($($arg)*);
14 }};
15}
16
17#[inline(always)]
18pub fn crash_on_debug() -> bool {
19 static CRASH_ON_DEBUG: Lazy<bool> = Lazy::new(|| {
20 in_test_configuration() || std::env::var("IOTA_ENABLE_DEBUG_ASSERTIONS").is_ok()
21 });
22
23 *CRASH_ON_DEBUG
24}
25
26#[cfg(msim)]
27pub mod intercept_debug_fatal {
28 use std::sync::{Arc, Mutex};
29
30 #[derive(Clone)]
31 pub struct DebugFatalCallback {
32 pub pattern: String,
33 pub callback: Arc<dyn Fn() + Send + Sync>,
34 }
35
36 thread_local! {
37 static INTERCEPT_DEBUG_FATAL: Mutex<Option<DebugFatalCallback>> = Mutex::new(None);
38 }
39
40 pub fn register_callback(message: &str, f: impl Fn() + Send + Sync + 'static) {
41 INTERCEPT_DEBUG_FATAL.with(|m| {
42 *m.lock().unwrap() = Some(DebugFatalCallback {
43 pattern: message.to_string(),
44 callback: Arc::new(f),
45 });
46 });
47 }
48
49 pub fn get_callback() -> Option<DebugFatalCallback> {
50 INTERCEPT_DEBUG_FATAL.with(|m| m.lock().unwrap().clone())
51 }
52}
53
54#[macro_export]
55macro_rules! register_debug_fatal_handler {
56 ($message:literal, $f:expr) => {
57 #[cfg(msim)]
58 $crate::logging::intercept_debug_fatal::register_callback($message, $f);
59
60 #[cfg(not(msim))]
61 {
62 let _ = $f;
64 }
65 };
66}
67
68#[cfg(not(target_arch = "wasm32"))]
69#[macro_export]
70macro_rules! debug_fatal {
71 ($($arg:tt)*) => {{
72 loop {
73 #[cfg(msim)]
74 {
75 if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
76 tracing::error!($($arg)*);
77 let msg = format!($($arg)*);
78 if msg.contains(&cb.pattern) {
79 (cb.callback)();
80 }
81 break;
82 }
83 }
84
85 if $crate::logging::crash_on_debug() {
86 $crate::fatal!($($arg)*);
87 } else {
88 let stacktrace = std::backtrace::Backtrace::capture();
89 tracing::error!(debug_fatal = true, stacktrace = ?stacktrace, $($arg)*);
90 let location = concat!(file!(), ':', line!());
91 if let Some(metrics) = iota_metrics::get_metrics() {
92 metrics.system_invariant_violations.with_label_values(&[location]).inc();
93 }
94 }
95 break;
96 }
97 }};
98}
99
100#[cfg(target_arch = "wasm32")]
103#[macro_export]
104macro_rules! debug_fatal {
105 ($($arg:tt)*) => {{
106 if $crate::logging::crash_on_debug() {
107 $crate::fatal!($($arg)*);
108 } else {
109 tracing::error!(debug_fatal = true, $($arg)*);
110 }
111 }};
112}
113
114mod tests {
115 #[test]
116 #[should_panic]
117 fn test_fatal() {
118 fatal!("This is a fatal error");
119 }
120
121 #[test]
122 #[should_panic]
123 fn test_debug_fatal() {
124 if cfg!(debug_assertions) {
125 debug_fatal!("This is a debug fatal error");
126 } else {
127 fatal!("This is a fatal error");
129 }
130 }
131
132 #[cfg(not(debug_assertions))]
133 #[test]
134 fn test_debug_fatal_release_mode() {
135 debug_fatal!("This is a debug fatal error");
136 }
137}