mc_sgx_panic_sys/panicking.rs
1// Copyright (c) 2023 The MobileCoin Foundation
2
3//! Common backend panic implementation
4//!
5//! This is a subset of the functionality available in Rust's std
6//! [panicking.rs](https://github.com/rust-lang/rust/blob/master/library/std/src/panicking.rs)
7//! module.
8
9#![allow(dead_code)]
10
11/// Determines whether the current thread is unwinding because of panic.
12pub(crate) fn panicking() -> bool {
13 !panic_count::count_is_zero()
14}
15
16pub mod panic_count {
17 //! Number of panics that are currently being handled on the current thread
18 //!
19 //! This deviates from
20 //! [panicking.rs](https://github.com/rust-lang/rust/blob/master/library/std/src/panicking.rs)
21 //! It does not:
22 //! - use `GLOBAL_PANIC_COUNT` count. The global count is meant to provide
23 //! an optimization on some platforms. It's not clear if SGX benefits from
24 //! this optimization and thus needing this complexity
25 //! - no `set_always_abort()`. The docs say this is for `libc::fork`.
26 //! Enclaves do not support forking or spawning child threads or processes
27 //! - use the `thread_local!` macro or the `::std::thread::LocalKey` type.
28 //! These are generic over any type that `needs_drop`, but
29 //! `usize` does not need drop
30 use core::cell::Cell;
31
32 /// Panic count for the current thread.
33 #[thread_local]
34 static LOCAL_PANIC_COUNT: Cell<usize> = Cell::new(0);
35
36 /// Increase the number of panics that are currently happening
37 ///
38 /// # Returns
39 /// The number of panics that are currently happening, including this
40 /// increase.
41 pub fn increase() -> usize {
42 let panics = LOCAL_PANIC_COUNT.get() + 1;
43 LOCAL_PANIC_COUNT.set(panics);
44 panics
45 }
46
47 /// Decrease the number of panics that are currently happening
48 pub fn decrease() {
49 let panics = LOCAL_PANIC_COUNT.get() - 1;
50 LOCAL_PANIC_COUNT.set(panics);
51 }
52
53 /// Get the current number of panics that are in process
54 #[must_use]
55 pub fn get_count() -> usize {
56 LOCAL_PANIC_COUNT.get()
57 }
58
59 /// Returns `true` when the there are no panics currently being handled
60 #[must_use]
61 pub fn count_is_zero() -> bool {
62 get_count() == 0
63 }
64
65 #[cfg(test)]
66 mod test {
67 use super::*;
68
69 /// Reset the panic count back to 0
70 ///
71 /// By default each test runs in a separate thread thus having their own
72 /// thread local copy of `LOCAL_PANIC_COUNT`. However per
73 /// https://github.com/rust-lang/rust/issues/58907 when running single
74 /// threaded the thread is re-used for each test execution so the value
75 /// should be reset to a known value on each test. This also means that
76 /// there isn't a reliable way to test that 0 is the true initial value.
77 fn reset_panic_count() {
78 LOCAL_PANIC_COUNT.set(0);
79 }
80
81 #[test]
82 fn count_is_zero_is_false_when_not_zero() {
83 reset_panic_count();
84 increase();
85 assert!(!count_is_zero());
86 }
87
88 #[test]
89 fn count_is_zero_is_true_when_zero() {
90 reset_panic_count();
91 assert!(count_is_zero());
92 increase();
93 assert!(!count_is_zero());
94 decrease();
95 assert!(count_is_zero());
96 }
97
98 #[test]
99 fn incrementing_one_at_a_time() {
100 reset_panic_count();
101 assert_eq!(increase(), 1);
102 assert_eq!(increase(), 2);
103 assert_eq!(increase(), 3);
104 assert_eq!(increase(), 4);
105 assert_eq!(get_count(), 4);
106 }
107
108 #[test]
109 fn decrementing_one_at_a_time() {
110 LOCAL_PANIC_COUNT.set(4);
111 assert_eq!(get_count(), 4);
112 decrease();
113 assert_eq!(get_count(), 3);
114 decrease();
115 assert_eq!(get_count(), 2);
116 decrease();
117 assert_eq!(get_count(), 1);
118 decrease();
119 assert_eq!(get_count(), 0);
120 }
121 }
122}
123
124#[cfg(test)]
125mod test {
126 use super::*;
127
128 /// Sets panic count is 0
129 /// Similar to the tests in mod `panic_count` tests need to call this prior
130 /// to testing to ensure correct behavior
131 fn clear_panic_count() {
132 while panic_count::get_count() != 0 {
133 panic_count::decrease();
134 }
135 }
136
137 #[test]
138 fn is_panicking_false_when_panic_count_zero() {
139 clear_panic_count();
140 assert!(!panicking());
141 }
142
143 #[test]
144 fn is_panicking_true_when_panic_count_gt_zero() {
145 clear_panic_count();
146
147 // First panic
148 panic_count::increase();
149 assert!(panicking());
150
151 // Second panic
152 panic_count::increase();
153 assert!(panicking());
154
155 // finished second panic
156 panic_count::decrease();
157 assert!(panicking());
158
159 // finished first panic
160 // NB that this one is *not* panicking since we've decreased back down
161 // to zero
162 panic_count::decrease();
163 assert!(!panicking());
164 }
165}