1use std::cell::Cell;
2#[cfg(test)]
3use std::sync::atomic::AtomicBool;
4use std::sync::atomic::AtomicU8;
5use std::sync::atomic::Ordering::Relaxed;
6use std::sync::{Arc, LazyLock};
7use std::{env, fmt};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[repr(u8)]
16pub enum RustBacktrace {
17 Disabled = 1,
19 Enabled = 2,
21 Full = 3,
23}
24
25impl RustBacktrace {
26 pub fn detect_opt() -> Option<Self> {
30 const NONE: u8 = u8::MAX;
31 const NOT_SET: u8 = 0;
32
33 static ENABLED: AtomicU8 = AtomicU8::new(NOT_SET);
34 if let Some(cached) = match ENABLED.load(Relaxed) {
35 1 => Some(Some(Self::Disabled)),
36 2 => Some(Some(Self::Enabled)),
37 3 => Some(Some(Self::Full)),
38 NONE => Some(None),
39 NOT_SET => None,
40 _ => unreachable!(),
41 } {
42 return cached;
43 }
44
45 let raw = env::var("RUST_LIB_BACKTRACE").or_else(|_| env::var("RUST_BACKTRACE"));
50 let enabled = match raw.as_deref() {
51 Ok("0") => Some(Self::Disabled),
52 Ok("full") => Some(Self::Full),
53 Ok(_) => Some(Self::Enabled),
54 Err(_) => None,
55 };
56 ENABLED.store(enabled.map_or(NONE, |f| f as u8), Relaxed);
57 enabled
58 }
59
60 pub fn detect_panic_opt() -> Option<Self> {
64 const NONE: u8 = u8::MAX;
65 const NOT_SET: u8 = 0;
66
67 static ENABLED: AtomicU8 = AtomicU8::new(NOT_SET);
68 if let Some(cached) = match ENABLED.load(Relaxed) {
69 1 => Some(Some(Self::Disabled)),
70 2 => Some(Some(Self::Enabled)),
71 3 => Some(Some(Self::Full)),
72 NONE => Some(None),
73 NOT_SET => None,
74 _ => unreachable!(),
75 } {
76 return cached;
77 }
78
79 let enabled = match env::var("RUST_BACKTRACE").as_deref() {
80 Ok("0") => Some(Self::Disabled),
81 Ok("full") => Some(Self::Full),
82 Ok(_) => Some(Self::Enabled),
83 Err(_) => None,
84 };
85 ENABLED.store(enabled.map_or(NONE, |f| f as u8), Relaxed);
86 enabled
87 }
88
89 #[inline]
91 #[must_use]
92 pub const fn is_enabled(self) -> bool {
93 matches!(self, Self::Full | Self::Enabled)
94 }
95
96 #[inline]
99 #[must_use]
100 pub const fn is_full(self) -> bool {
101 matches!(self, Self::Full)
102 }
103}
104
105thread_local! {
106 static OVERRIDE: Cell<Option<RustBacktrace>> = const { Cell::new(None) };
109}
110
111#[inline]
118pub fn set_rust_backtrace_override(value: RustBacktrace) {
119 OVERRIDE.with(|o| o.set(Some(value)));
120}
121
122#[inline]
125pub fn clear_rust_backtrace_override() {
126 OVERRIDE.with(|o| o.set(None));
127}
128
129#[must_use]
136#[inline]
137pub fn rust_backtrace() -> RustBacktrace {
138 if let Some(over) = OVERRIDE.with(Cell::get) {
139 return over;
140 }
141 RustBacktrace::detect_opt().unwrap_or(RustBacktrace::Disabled)
142}
143
144#[must_use]
147#[inline]
148pub fn rust_panic_backtrace() -> RustBacktrace {
149 if let Some(over) = OVERRIDE.with(Cell::get) {
150 return over;
151 }
152 RustBacktrace::detect_panic_opt().unwrap_or(RustBacktrace::Disabled)
153}
154
155#[inline]
158pub fn with_rust_backtrace_override<R>(value: RustBacktrace, f: impl FnOnce() -> R) -> R {
159 struct Restore(Option<RustBacktrace>);
160 impl Drop for Restore {
161 fn drop(&mut self) {
162 OVERRIDE.with(|o| o.set(self.0));
163 }
164 }
165 let _restore = Restore(OVERRIDE.with(Cell::get));
166 OVERRIDE.with(|o| o.set(Some(value)));
167 f()
168}
169
170#[derive(Clone)]
173enum Inner {
174 Captured(Arc<Lazy>),
175 Disabled(backtrace::Backtrace), }
177
178#[derive(Clone)]
186pub struct Backtrace {
187 inner: Inner,
188 marker: Option<crate::marker::TraceMarker>,
189}
190
191impl crate::Capturable for Backtrace {
192 #[inline]
193 fn capture() -> Self {
194 if rust_backtrace().is_enabled() {
195 Self {
196 inner: Inner::Captured(Arc::new(Lazy::new(helper::lazy_resolve(Capture {
197 backtrace: backtrace::Backtrace::new_unresolved(),
198 })))),
199 marker: crate::marker::current(),
200 }
201 } else {
202 Self {
203 inner: Inner::Disabled(backtrace::Backtrace::from(vec![])),
204 marker: None,
205 }
206 }
207 }
208
209 #[inline]
210 fn capture_or_extract(source: &dyn crate::Diagnostic) -> Self {
211 match source.oopsie_backtrace() {
212 Some(trace) if trace.is_captured() => trace.clone(),
216 _ => Self::capture(),
217 }
218 }
219}
220
221pub const CORE_SRC_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/src/");
224
225impl fmt::Debug for Backtrace {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 fmt::Debug::fmt(self.as_backtrace(), f)
228 }
229}
230
231impl Backtrace {
232 #[doc(hidden)]
239 #[must_use]
240 #[inline]
241 pub fn as_backtrace(&self) -> &backtrace::Backtrace {
242 match &self.inner {
243 Inner::Captured(bt) => &bt.force().backtrace,
244 Inner::Disabled(bt) => bt,
245 }
246 }
247
248 #[must_use]
254 #[inline]
255 pub const fn is_captured(&self) -> bool {
256 match &self.inner {
257 Inner::Captured(_) => true,
258 Inner::Disabled(_) => false,
259 }
260 }
261
262 #[doc(hidden)]
266 #[must_use]
267 #[inline]
268 pub fn frames(&self) -> &[backtrace::BacktraceFrame] {
269 self.as_backtrace().frames()
270 }
271
272 #[must_use]
280 pub fn marker_hidden_frames(&self) -> Option<usize> {
281 let marker = self.marker.as_ref()?;
282 let frames = self.frames();
283 let cut = marker.cut_len(frames);
284 if cut == 0 || cut >= frames.len() {
285 return None;
286 }
287 let rendered = |frames: &[backtrace::BacktraceFrame]| {
292 frames
293 .iter()
294 .map(|frame| frame.symbols().len())
295 .sum::<usize>()
296 };
297 let hidden = rendered(&frames[frames.len() - cut..]);
298 (hidden > 0 && hidden < rendered(frames)).then_some(hidden)
299 }
300
301 #[inline]
308 pub fn resolve(&self) {
309 let _ = self.as_backtrace();
310 }
311
312 #[cfg(test)]
313 fn is_resolved(&self) -> bool {
314 match &self.inner {
315 Inner::Captured(bt) => bt.is_resolved(),
316 Inner::Disabled(_) => true,
317 }
318 }
319}
320
321struct Capture {
322 backtrace: backtrace::Backtrace,
323}
324
325struct Lazy {
328 cell: LazyLock<Capture, helper::LazyResolve>,
329 #[cfg(test)]
334 forced: AtomicBool,
335}
336
337impl Lazy {
338 fn new(resolve: helper::LazyResolve) -> Self {
339 Self {
340 cell: LazyLock::new(resolve),
341 #[cfg(test)]
342 forced: AtomicBool::new(false),
343 }
344 }
345
346 #[inline]
348 fn force(&self) -> &Capture {
349 let capture = &*self.cell;
350 #[cfg(test)]
351 self.forced.store(true, Relaxed);
352 capture
353 }
354
355 #[cfg(test)]
356 fn is_resolved(&self) -> bool {
357 self.forced.load(Relaxed)
358 }
359}
360
361mod helper {
362 use std::panic::UnwindSafe;
363
364 use super::*;
365
366 pub(super) type LazyResolve = Box<dyn FnOnce() -> Capture + Send + Sync + UnwindSafe>;
375
376 pub(super) fn lazy_resolve(mut capture: Capture) -> LazyResolve {
377 Box::new(move || {
378 capture.backtrace.resolve();
379 capture
380 })
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387 use crate::{Capturable, Diagnostic};
388
389 #[derive(Debug)]
390 struct ErrorWithBacktrace {
391 backtrace: Backtrace,
392 }
393
394 impl fmt::Display for ErrorWithBacktrace {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 write!(f, "error with backtrace")
397 }
398 }
399
400 impl std::error::Error for ErrorWithBacktrace {}
401
402 impl Diagnostic for ErrorWithBacktrace {
403 fn oopsie_backtrace(&self) -> Option<&Backtrace> {
404 Some(&self.backtrace)
405 }
406 }
407
408 #[derive(Debug)]
409 struct ErrorWithoutBacktrace;
410
411 impl fmt::Display for ErrorWithoutBacktrace {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 write!(f, "error without backtrace")
414 }
415 }
416
417 impl std::error::Error for ErrorWithoutBacktrace {}
418
419 impl Diagnostic for ErrorWithoutBacktrace {}
420
421 #[test]
422 fn test_rust_backtrace_override() {
423 set_rust_backtrace_override(RustBacktrace::Enabled);
424 assert_eq!(rust_backtrace(), RustBacktrace::Enabled);
425 }
426
427 #[test]
428 fn test_backtrace_capture_produces_backtrace() {
429 let bt = Backtrace::capture();
430 let _ = bt.as_backtrace().frames();
432 }
433
434 #[test]
435 fn test_backtrace_capture_or_extract_with_existing_backtrace() {
436 let original_bt = Backtrace::capture();
437 let error = ErrorWithBacktrace {
438 backtrace: original_bt.clone(),
439 };
440
441 let extracted = Backtrace::capture_or_extract(&error);
443 assert_eq!(
445 extracted.as_backtrace().frames().len(),
446 original_bt.as_backtrace().frames().len()
447 );
448 }
449
450 #[test]
451 fn test_backtrace_capture_or_extract_without_existing_backtrace() {
452 let error = ErrorWithoutBacktrace;
453
454 let captured = Backtrace::capture_or_extract(&error);
456 let _ = captured.as_backtrace().frames();
458 }
459
460 #[test]
461 fn test_backtrace_inner_returns_reference() {
462 let bt = Backtrace::capture();
463 let inner = bt.as_backtrace();
464 let _ = inner.frames();
466 }
467
468 #[test]
469 fn test_capture_is_empty_when_disabled() {
470 set_rust_backtrace_override(RustBacktrace::Disabled);
471 let bt = Backtrace::capture();
472 clear_rust_backtrace_override();
473 assert!(
474 bt.frames().is_empty(),
475 "disabled capture must record no frames, got {}",
476 bt.frames().len()
477 );
478 }
479
480 #[test]
481 fn test_capture_or_extract_is_empty_when_disabled() {
482 set_rust_backtrace_override(RustBacktrace::Disabled);
483 let bt = Backtrace::capture_or_extract(&ErrorWithoutBacktrace);
486 clear_rust_backtrace_override();
487 assert!(bt.frames().is_empty());
488 }
489
490 #[test]
491 fn capture_or_extract_recaptures_over_empty_source_backtrace() {
492 let src = with_rust_backtrace_override(RustBacktrace::Disabled, || ErrorWithBacktrace {
493 backtrace: Backtrace::capture(),
494 });
495 assert!(
496 src.backtrace.frames().is_empty(),
497 "precondition: source bt empty"
498 );
499 let extracted = with_rust_backtrace_override(RustBacktrace::Enabled, || {
500 <Backtrace as Capturable>::capture_or_extract(&src)
501 });
502 assert!(!extracted.frames().is_empty());
503 }
504
505 #[test]
506 fn clone_does_not_force_resolution() {
507 let bt = with_rust_backtrace_override(RustBacktrace::Enabled, Backtrace::capture);
508 let clone = bt.clone();
509 assert!(!bt.is_resolved(), "clone must not symbolicate");
510 assert!(!clone.is_resolved());
511 let _ = clone.frames();
512 assert!(bt.is_resolved(), "clones share a single resolution");
513 }
514
515 #[test]
516 fn debug_never_renders_empty_for_nonempty_capture() {
517 let bt = with_rust_backtrace_override(RustBacktrace::Enabled, Backtrace::capture);
518 assert!(!bt.frames().is_empty());
519 let rendered = with_rust_backtrace_override(RustBacktrace::Enabled, || format!("{bt:?}"));
520 assert!(rendered.contains("0:"), "rendered: {rendered}");
523 }
524
525 #[test]
526 fn test_capture_records_frames_when_enabled() {
527 set_rust_backtrace_override(RustBacktrace::Enabled);
528 let bt = Backtrace::capture();
529 clear_rust_backtrace_override();
530 assert!(!bt.frames().is_empty());
533 }
534
535 #[test]
536 fn with_override_scopes_and_restores() {
537 let baseline = rust_backtrace();
538 let panic_baseline = rust_panic_backtrace();
539 let inside = with_rust_backtrace_override(RustBacktrace::Full, rust_backtrace);
540 assert_eq!(inside, RustBacktrace::Full);
541 assert_eq!(rust_backtrace(), baseline);
542 let inside = with_rust_backtrace_override(RustBacktrace::Disabled, rust_panic_backtrace);
543 assert_eq!(inside, RustBacktrace::Disabled);
544 assert_eq!(rust_panic_backtrace(), panic_baseline);
545 }
546
547 #[test]
548 fn with_override_restores_on_unwind() {
549 let baseline = rust_backtrace();
550 let _ = std::panic::catch_unwind(|| {
551 with_rust_backtrace_override(RustBacktrace::Full, || panic!("boom"))
552 });
553 assert_eq!(rust_backtrace(), baseline);
554 }
555
556 const _: () = {
557 const fn is_capturable<T: crate::Capturable>() {}
559 is_capturable::<Backtrace>();
560 };
561
562 #[test]
563 fn capture_embeds_marker_and_computes_hidden_frames() {
564 crate::with_rust_backtrace_override(RustBacktrace::Enabled, || {
565 let _restore_guard = {
566 let prev = crate::marker::current();
568 scopeguard(prev)
569 };
570 let _ = crate::__private::set_marker();
571 let bt = <Backtrace as crate::Capturable>::capture();
572 let hidden = bt
573 .marker_hidden_frames()
574 .expect("same-thread marker must produce a cut");
575 assert!(hidden > 0);
576 let total_rendered: usize = bt.frames().iter().map(|f| f.symbols().len()).sum();
579 assert!(hidden < total_rendered);
580 });
581 }
582
583 #[test]
584 fn capture_without_marker_has_no_hidden_frames() {
585 crate::with_rust_backtrace_override(RustBacktrace::Enabled, || {
586 let _restore_guard = scopeguard(crate::marker::current());
587 crate::__private::restore_marker(None);
588 let bt = <Backtrace as crate::Capturable>::capture();
589 assert!(bt.marker_hidden_frames().is_none());
590 });
591 }
592
593 #[test]
594 fn marker_does_not_cross_threads() {
595 crate::with_rust_backtrace_override(RustBacktrace::Enabled, || {
596 let _ = crate::__private::set_marker();
597 let bt = std::thread::spawn(|| {
598 crate::with_rust_backtrace_override(RustBacktrace::Enabled, || {
599 <Backtrace as crate::Capturable>::capture()
600 })
601 })
602 .join()
603 .unwrap();
604 assert!(bt.marker_hidden_frames().is_none());
606 });
607 }
608
609 fn scopeguard(prev: Option<crate::marker::TraceMarker>) -> impl Drop {
611 struct Restore(Option<crate::marker::TraceMarker>);
612 impl Drop for Restore {
613 fn drop(&mut self) {
614 crate::__private::restore_marker(self.0.take());
615 }
616 }
617 Restore(prev)
618 }
619}