1use crate::in_test_configuration;
5use once_cell::sync::Lazy;
6
7#[macro_export]
8macro_rules! fatal {
9 ($msg:literal $(, $arg:expr)*) => {{
10 if $crate::in_antithesis() {
11 let full_msg = format!($msg $(, $arg)*);
12 let json = $crate::logging::json!({ "message": full_msg });
13 $crate::logging::assert_unreachable_antithesis!($msg, &json);
14 }
15 tracing::error!(fatal = true, $msg $(, $arg)*);
16 panic!($msg $(, $arg)*);
17 }};
18}
19
20pub use antithesis_sdk::assert_reachable as assert_reachable_antithesis;
21pub use antithesis_sdk::assert_sometimes as assert_sometimes_antithesis;
22pub use antithesis_sdk::assert_unreachable as assert_unreachable_antithesis;
23
24pub use serde_json::json;
25
26#[inline(always)]
27pub fn crash_on_debug() -> bool {
28 static CRASH_ON_DEBUG: Lazy<bool> = Lazy::new(|| {
29 in_test_configuration() || std::env::var("SUI_ENABLE_DEBUG_ASSERTIONS").is_ok()
30 });
31
32 *CRASH_ON_DEBUG
33}
34
35#[cfg(msim)]
36pub mod intercept_debug_fatal {
37 use std::sync::{Arc, Mutex};
38
39 #[derive(Clone)]
40 pub struct DebugFatalCallback {
41 pub pattern: String,
42 pub callback: Arc<dyn Fn() + Send + Sync>,
43 }
44
45 thread_local! {
46 static INTERCEPT_DEBUG_FATAL: Mutex<Option<DebugFatalCallback>> = Mutex::new(None);
47 }
48
49 pub fn register_callback(message: &str, f: impl Fn() + Send + Sync + 'static) {
50 INTERCEPT_DEBUG_FATAL.with(|m| {
51 *m.lock().unwrap() = Some(DebugFatalCallback {
52 pattern: message.to_string(),
53 callback: Arc::new(f),
54 });
55 });
56 }
57
58 pub fn get_callback() -> Option<DebugFatalCallback> {
59 INTERCEPT_DEBUG_FATAL.with(|m| m.lock().unwrap().clone())
60 }
61}
62
63#[macro_export]
64macro_rules! register_debug_fatal_handler {
65 ($message:literal, $f:expr) => {
66 #[cfg(msim)]
67 $crate::logging::intercept_debug_fatal::register_callback($message, $f);
68
69 #[cfg(not(msim))]
70 {
71 let _ = $f;
73 }
74 };
75}
76
77#[macro_export]
83macro_rules! debug_fatal_at {
84 ($location:expr, $msg:literal $(, $arg:expr)*) => {{
85 loop {
86 #[cfg(msim)]
87 {
88 if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
89 tracing::error!($msg $(, $arg)*);
90 let msg = format!($msg $(, $arg)*);
91 if msg.contains(&cb.pattern) {
92 (cb.callback)();
93 }
94 break;
95 }
96 }
97
98 if !$crate::in_antithesis() && $crate::logging::crash_on_debug() {
101 $crate::fatal!($msg $(, $arg)*);
102 } else {
103 let stacktrace = std::backtrace::Backtrace::capture();
104 tracing::error!(debug_fatal = true, stacktrace = ?stacktrace, $msg $(, $arg)*);
105 let location: &str = $location;
106 if let Some(metrics) = mysten_metrics::get_metrics() {
107 metrics.system_invariant_violations.with_label_values(&[location]).inc();
108 }
109 if $crate::in_antithesis() {
110 let full_msg = format!($msg $(, $arg)*);
113 let json = $crate::logging::json!({ "message": full_msg });
114 $crate::logging::assert_unreachable_antithesis!($msg, &json);
115 }
116 }
117 break;
118 }
119 }};
120}
121
122#[macro_export]
123macro_rules! debug_fatal {
124 ($msg:literal $(, $arg:expr)*) => {{
126 $crate::debug_fatal_at!(concat!(file!(), ':', line!()), $msg $(, $arg)*);
127 }};
128}
129
130#[macro_export]
131macro_rules! debug_fatal_no_invariant {
132 ($msg:literal $(, $arg:expr)*) => {{
133 loop {
134 #[cfg(msim)]
135 {
136 if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
137 tracing::error!($msg $(, $arg)*);
138 let msg = format!($msg $(, $arg)*);
139 if msg.contains(&cb.pattern) {
140 (cb.callback)();
141 }
142 break;
143 }
144 }
145
146 if !$crate::in_antithesis() && $crate::logging::crash_on_debug() {
147 $crate::fatal!($msg $(, $arg)*);
148 } else {
149 tracing::error!($msg $(, $arg)*);
150 if $crate::in_antithesis() {
151 let full_msg = format!($msg $(, $arg)*);
152 let json = $crate::logging::json!({ "message": full_msg });
153 $crate::logging::assert_unreachable_antithesis!($msg, &json);
154 }
155 }
156 break;
157 }
158 }};
159}
160
161#[macro_export]
162macro_rules! assert_reachable {
163 () => {
164 $crate::logging::assert_reachable!("");
165 };
166 ($message:literal) => {{
167 if !cfg!(msim) {
169 $crate::logging::assert_reachable_antithesis!($message);
170 } else {
171 $crate::assert_reachable_simtest!($message);
172 }
173 }};
174}
175
176#[macro_export]
177macro_rules! assert_sometimes {
178 ($expr:expr, $message:literal) => {{
179 if !cfg!(msim) {
181 $crate::logging::assert_sometimes_antithesis!($expr, $message);
182 } else {
183 $crate::assert_sometimes_simtest!($expr, $message);
184 }
185 }};
186}
187
188mod tests {
189 #[test]
190 #[should_panic]
191 fn test_fatal() {
192 fatal!("This is a fatal error");
193 }
194
195 #[test]
196 #[should_panic]
197 fn test_debug_fatal() {
198 if cfg!(debug_assertions) {
199 debug_fatal!("This is a debug fatal error");
200 } else {
201 fatal!("This is a fatal error");
203 }
204 }
205
206 #[cfg(not(debug_assertions))]
207 #[test]
208 fn test_debug_fatal_release_mode() {
209 debug_fatal!("This is a debug fatal error");
210 }
211
212 #[test]
213 fn test_assert_sometimes_side_effects() {
214 let mut x = 0;
215
216 let mut inc = || {
217 x += 1;
218 true
219 };
220
221 assert_sometimes!(inc(), "");
222 assert_eq!(x, 1);
223 }
224}