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}