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}