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 static INTERCEPT_DEBUG_FATAL: Mutex<Option<DebugFatalCallback>> = Mutex::new(None);
46
47 pub fn register_callback(message: &str, f: impl Fn() + Send + Sync + 'static) {
48 *INTERCEPT_DEBUG_FATAL.lock().unwrap() = Some(DebugFatalCallback {
49 pattern: message.to_string(),
50 callback: Arc::new(f),
51 });
52 }
53
54 pub fn get_callback() -> Option<DebugFatalCallback> {
55 INTERCEPT_DEBUG_FATAL.lock().unwrap().clone()
56 }
57}
58
59#[macro_export]
60macro_rules! register_debug_fatal_handler {
61 ($message:literal, $f:expr) => {
62 #[cfg(msim)]
63 $crate::logging::intercept_debug_fatal::register_callback($message, $f);
64
65 #[cfg(not(msim))]
66 {
67 let _ = $f;
69 }
70 };
71}
72
73#[macro_export]
77macro_rules! debug_fatal_with_metric {
78 ($record:expr, $msg:literal $(, $arg:expr)*) => {{
79 loop {
80 #[cfg(msim)]
81 {
82 if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
83 tracing::error!($msg $(, $arg)*);
84 let msg = format!($msg $(, $arg)*);
85 if msg.contains(&cb.pattern) {
86 (cb.callback)();
87 }
88 break;
89 }
90 }
91
92 if !$crate::in_antithesis() && $crate::logging::crash_on_debug() {
95 $crate::fatal!($msg $(, $arg)*);
96 } else {
97 let stacktrace = std::backtrace::Backtrace::capture();
98 tracing::error!(debug_fatal = true, stacktrace = ?stacktrace, $msg $(, $arg)*);
99 if let Some(metrics) = mysten_metrics::get_metrics() {
100 ($record)(metrics);
101 }
102 if $crate::in_antithesis() {
103 let full_msg = format!($msg $(, $arg)*);
106 let json = $crate::logging::json!({ "message": full_msg });
107 $crate::logging::assert_unreachable_antithesis!($msg, &json);
108 }
109 }
110 break;
111 }
112 }};
113}
114
115#[macro_export]
121macro_rules! debug_fatal_at {
122 ($location:expr, $msg:literal $(, $arg:expr)*) => {{
123 $crate::debug_fatal_with_metric!(
124 |metrics: &mysten_metrics::Metrics| {
125 let location: &str = $location;
126 metrics.system_invariant_violations.with_label_values(&[location]).inc();
127 },
128 $msg $(, $arg)*
129 );
130 }};
131}
132
133#[macro_export]
134macro_rules! debug_fatal {
135 ($msg:literal $(, $arg:expr)*) => {{
137 $crate::debug_fatal_at!(concat!(file!(), ':', line!()), $msg $(, $arg)*);
138 }};
139}
140
141#[macro_export]
142macro_rules! debug_fatal_no_invariant {
143 ($msg:literal $(, $arg:expr)*) => {{
144 loop {
145 #[cfg(msim)]
146 {
147 if let Some(cb) = $crate::logging::intercept_debug_fatal::get_callback() {
148 tracing::error!($msg $(, $arg)*);
149 let msg = format!($msg $(, $arg)*);
150 if msg.contains(&cb.pattern) {
151 (cb.callback)();
152 }
153 break;
154 }
155 }
156
157 if !$crate::in_antithesis() && $crate::logging::crash_on_debug() {
158 $crate::fatal!($msg $(, $arg)*);
159 } else {
160 tracing::error!($msg $(, $arg)*);
161 if $crate::in_antithesis() {
162 let full_msg = format!($msg $(, $arg)*);
163 let json = $crate::logging::json!({ "message": full_msg });
164 $crate::logging::assert_unreachable_antithesis!($msg, &json);
165 }
166 }
167 break;
168 }
169 }};
170}
171
172#[macro_export]
179macro_rules! assert_reachable {
180 () => {
181 $crate::logging::assert_reachable!("");
182 };
183 ($message:literal) => {{
184 if !cfg!(msim) {
186 $crate::logging::assert_reachable_antithesis!($message);
187 } else {
188 $crate::assert_reachable_simtest!($message);
189 }
190 }};
191}
192
193#[macro_export]
194macro_rules! assert_sometimes {
195 ($expr:expr, $message:literal) => {{
196 if !cfg!(msim) {
198 $crate::logging::assert_sometimes_antithesis!($expr, $message);
199 } else {
200 $crate::assert_sometimes_simtest!($expr, $message);
201 }
202 }};
203}
204
205mod tests {
206 #[test]
207 #[should_panic]
208 fn test_fatal() {
209 fatal!("This is a fatal error");
210 }
211
212 #[test]
213 #[should_panic]
214 fn test_debug_fatal() {
215 if cfg!(debug_assertions) {
216 debug_fatal!("This is a debug fatal error");
217 } else {
218 fatal!("This is a fatal error");
220 }
221 }
222
223 #[cfg(not(debug_assertions))]
224 #[test]
225 fn test_debug_fatal_release_mode() {
226 debug_fatal!("This is a debug fatal error");
227 }
228
229 #[test]
230 fn test_assert_sometimes_side_effects() {
231 let mut x = 0;
232
233 let mut inc = || {
234 x += 1;
235 true
236 };
237
238 assert_sometimes!(inc(), "");
239 assert_eq!(x, 1);
240 }
241}