bevy_ecs/error/
bevy_error.rs1use alloc::boxed::Box;
2use core::{
3    error::Error,
4    fmt::{Debug, Display},
5};
6
7pub struct BevyError {
29    inner: Box<InnerBevyError>,
30}
31
32impl BevyError {
33    pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
35        self.inner.error.downcast_ref::<E>()
36    }
37
38    fn format_backtrace(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        #[cfg(feature = "backtrace")]
40        {
41            let f = _f;
42            let backtrace = &self.inner.backtrace;
43            if let std::backtrace::BacktraceStatus::Captured = backtrace.status() {
44                let full_backtrace = std::env::var("BEVY_BACKTRACE").is_ok_and(|val| val == "full");
45
46                let backtrace_str = alloc::string::ToString::to_string(backtrace);
47                let mut skip_next_location_line = false;
48                for line in backtrace_str.split('\n') {
49                    if !full_backtrace {
50                        if skip_next_location_line {
51                            if line.starts_with("             at") {
52                                continue;
53                            }
54                            skip_next_location_line = false;
55                        }
56                        if line.contains("std::backtrace_rs::backtrace::") {
57                            skip_next_location_line = true;
58                            continue;
59                        }
60                        if line.contains("std::backtrace::Backtrace::") {
61                            skip_next_location_line = true;
62                            continue;
63                        }
64                        if line.contains("<bevy_ecs::error::bevy_error::BevyError as core::convert::From<E>>::from") {
65                            skip_next_location_line = true;
66                            continue;
67                        }
68                        if line.contains("<core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual") {
69                            skip_next_location_line = true;
70                            continue;
71                        }
72                        if line.contains("__rust_begin_short_backtrace") {
73                            break;
74                        }
75                        if line.contains("bevy_ecs::observer::Observers::invoke::{{closure}}") {
76                            break;
77                        }
78                    }
79                    writeln!(f, "{line}")?;
80                }
81                if !full_backtrace {
82                    if std::thread::panicking() {
83                        SKIP_NORMAL_BACKTRACE.set(true);
84                    }
85                    writeln!(f, "{FILTER_MESSAGE}")?;
86                }
87            }
88        }
89        Ok(())
90    }
91}
92
93struct InnerBevyError {
99    error: Box<dyn Error + Send + Sync + 'static>,
100    #[cfg(feature = "backtrace")]
101    backtrace: std::backtrace::Backtrace,
102}
103
104impl<E> From<E> for BevyError
106where
107    Box<dyn Error + Send + Sync + 'static>: From<E>,
108{
109    #[cold]
110    fn from(error: E) -> Self {
111        BevyError {
112            inner: Box::new(InnerBevyError {
113                error: error.into(),
114                #[cfg(feature = "backtrace")]
115                backtrace: std::backtrace::Backtrace::capture(),
116            }),
117        }
118    }
119}
120
121impl Display for BevyError {
122    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123        writeln!(f, "{}", self.inner.error)?;
124        self.format_backtrace(f)?;
125        Ok(())
126    }
127}
128
129impl Debug for BevyError {
130    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
131        writeln!(f, "{:?}", self.inner.error)?;
132        self.format_backtrace(f)?;
133        Ok(())
134    }
135}
136
137#[cfg(feature = "backtrace")]
138const FILTER_MESSAGE: &str = "note: Some \"noisy\" backtrace lines have been filtered out. Run with `BEVY_BACKTRACE=full` for a verbose backtrace.";
139
140#[cfg(feature = "backtrace")]
141std::thread_local! {
142    static SKIP_NORMAL_BACKTRACE: core::cell::Cell<bool> =
143        const { core::cell::Cell::new(false) };
144}
145
146#[cfg(feature = "backtrace")]
148#[expect(clippy::print_stdout, reason = "Allowed behind `std` feature gate.")]
149pub fn bevy_error_panic_hook(
150    current_hook: impl Fn(&std::panic::PanicHookInfo),
151) -> impl Fn(&std::panic::PanicHookInfo) {
152    move |info| {
153        if SKIP_NORMAL_BACKTRACE.replace(false) {
154            if let Some(payload) = info.payload().downcast_ref::<&str>() {
155                std::println!("{payload}");
156            } else if let Some(payload) = info.payload().downcast_ref::<alloc::string::String>() {
157                std::println!("{payload}");
158            }
159            return;
160        }
161
162        current_hook(info);
163    }
164}
165
166#[cfg(test)]
167mod tests {
168
169    #[test]
170    #[cfg(not(miri))] #[cfg(not(windows))] fn filtered_backtrace_test() {
173        fn i_fail() -> crate::error::Result {
174            let _: usize = "I am not a number".parse()?;
175            Ok(())
176        }
177
178        unsafe { std::env::set_var("RUST_BACKTRACE", "1") };
183
184        let error = i_fail().err().unwrap();
185        let debug_message = alloc::format!("{error:?}");
186        let mut lines = debug_message.lines().peekable();
187        assert_eq!(
188            "ParseIntError { kind: InvalidDigit }",
189            lines.next().unwrap()
190        );
191
192        let mut skip = false;
194        if let Some(line) = lines.peek() {
195            if &line[6..] == "std::backtrace::Backtrace::create" {
196                skip = true;
197            }
198        }
199
200        if skip {
201            lines.next().unwrap();
202        }
203
204        let expected_lines = alloc::vec![
205            "bevy_ecs::error::bevy_error::tests::filtered_backtrace_test::i_fail",
206            "bevy_ecs::error::bevy_error::tests::filtered_backtrace_test",
207            "bevy_ecs::error::bevy_error::tests::filtered_backtrace_test::{{closure}}",
208            "core::ops::function::FnOnce::call_once",
209        ];
210
211        for expected in expected_lines {
212            let line = lines.next().unwrap();
213            assert_eq!(&line[6..], expected);
214            let mut skip = false;
215            if let Some(line) = lines.peek() {
216                if line.starts_with("             at") {
217                    skip = true;
218                }
219            }
220
221            if skip {
222                lines.next().unwrap();
223            }
224        }
225
226        let mut skip = false;
228        if let Some(line) = lines.peek() {
229            if &line[6..] == "core::ops::function::FnOnce::call_once" {
230                skip = true;
231            }
232        }
233        if skip {
234            lines.next().unwrap();
235        }
236        let mut skip = false;
237        if let Some(line) = lines.peek() {
238            if line.starts_with("             at") {
239                skip = true;
240            }
241        }
242
243        if skip {
244            lines.next().unwrap();
245        }
246        assert_eq!(super::FILTER_MESSAGE, lines.next().unwrap());
247        assert!(lines.next().is_none());
248    }
249}