1use core::fmt::{Debug, Write};
2use core::panic::Location;
3
4use crate::{AnsiColorScheme, AnsiColored, DebugAnsiColored, PanicDetector};
5
6#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
34pub struct UnwindContextWithFmt<W: Write, T: Debug + DebugAnsiColored, P: PanicDetector> {
35 data: T,
36 writer: W,
37 panic_detector: P,
38 color_scheme: Option<&'static AnsiColorScheme>,
39 location: &'static Location<'static>,
40}
41
42impl<W: Write, T: Debug + DebugAnsiColored, P: PanicDetector> Drop
43 for UnwindContextWithFmt<W, T, P>
44{
45 #[inline]
46 fn drop(&mut self) {
47 if self.panic_detector.is_panicking() {
48 self.print();
49 }
50 }
51}
52
53impl<W: Write, T: Debug + DebugAnsiColored, P: PanicDetector> UnwindContextWithFmt<W, T, P> {
54 #[inline]
62 #[must_use = "\
63 if unused, the `UnwindContextWithFmt` will immediately drop,
64 consider binding the `UnwindContextWithFmt` like `let _ctx = ...`.
65 "]
66 #[track_caller]
67 pub fn new(
68 data: T,
69 writer: W,
70 panic_detector: P,
71 color_scheme: Option<&'static AnsiColorScheme>,
72 ) -> Self {
73 Self {
74 data,
75 writer,
76 panic_detector,
77 color_scheme,
78 location: Location::caller(),
79 }
80 }
81
82 #[cold]
87 #[inline(never)]
88 pub fn print(&mut self) {
89 if let Some(color_scheme) = self.color_scheme {
90 let _ = writeln!(
91 self.writer,
92 "{:?}\n at {}{}:{}:{}{}",
93 AnsiColored::new(&self.data, color_scheme),
94 color_scheme.location,
95 self.location.file(),
96 self.location.line(),
97 self.location.column(),
98 color_scheme.default,
99 );
100 } else {
101 let _ = writeln!(
102 self.writer,
103 "{:?}\n at {}:{}:{}",
104 self.data,
105 self.location.file(),
106 self.location.line(),
107 self.location.column(),
108 );
109 }
110 }
111}
112
113#[macro_export]
170macro_rules! unwind_context_with_fmt {
171 (
172 ( $( $context:tt )* )
173 , writer = $writer:expr
174 , panic_detector = $panic_detector:expr
175 $(, color_scheme = $color_scheme:expr )?
176 $(,)?
177 ) => {
178 $crate::UnwindContextWithFmt::new(
179 $crate::build_unwind_context_data!( $($context)* ),
180 $writer,
181 $panic_detector,
182 $crate::expr_or_default_expr!(
183 $( $color_scheme )?,
184 $crate::get_default_color_scheme_if_enabled()
185 ),
186 )
187 };
188}
189
190#[macro_export]
254macro_rules! debug_unwind_context_with_fmt {
255 ( $( $tokens:tt )* ) => { $crate::debug_unwind_context_with_fmt_impl!( $($tokens)* ) };
256}
257
258#[doc(hidden)]
259#[cfg(debug_assertions)]
260#[macro_export]
261macro_rules! debug_unwind_context_with_fmt_impl {
262 ( $( $tokens:tt )* ) => { $crate::unwind_context_with_fmt!( $($tokens)* ) };
263}
264
265#[doc(hidden)]
266#[cfg(not(debug_assertions))]
267#[macro_export]
268macro_rules! debug_unwind_context_with_fmt_impl {
269 ($($tokens:tt)*) => {
270 ()
271 };
272}
273
274#[cfg(test)]
275mod tests {
276 #[cfg(feature = "std")]
277 use core::fmt::Result as FmtResult;
278 use core::fmt::Write as FmtWrite;
279 use core::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
280 #[cfg(feature = "std")]
281 use std::borrow::ToOwned;
282 #[cfg(feature = "std")]
283 use std::string::String;
284 #[cfg(feature = "std")]
285 use std::sync::mpsc;
286
287 use crate::test_common::{check_location_part, TEST_COLOR_SCHEME};
288 #[cfg(feature = "std")]
289 use crate::test_util::collect_string_from_recv;
290 use crate::test_util::{FixedBufWriter, PatternMatcher};
291 #[cfg(feature = "std")]
292 use crate::StdPanicDetector;
293 use crate::{AnsiColorScheme, PanicDetector};
294
295 #[derive(Clone, Debug)]
296 pub struct DummyPanicDetector<'a> {
297 is_panicking: &'a AtomicBool,
298 }
299
300 impl PanicDetector for DummyPanicDetector<'_> {
301 fn is_panicking(&self) -> bool {
302 self.is_panicking.load(AtomicOrdering::Relaxed)
303 }
304 }
305
306 #[cfg(feature = "std")]
307 #[derive(Clone, Debug)]
308 pub struct ChannelWriter(mpsc::Sender<String>);
309
310 #[cfg(feature = "std")]
311 impl FmtWrite for ChannelWriter {
312 #[allow(clippy::unwrap_used)]
313 fn write_str(&mut self, buf: &str) -> FmtResult {
314 self.0.send(buf.to_owned()).unwrap();
315 Ok(())
316 }
317 }
318
319 fn get_min_line() -> u32 {
322 line!()
323 }
324
325 #[allow(clippy::unwrap_used)]
327 fn func1<W: FmtWrite, P: Clone + PanicDetector>(
328 foo: usize,
329 bar: &str,
330 writer1: &mut W,
331 writer2: &mut W,
332 writer3: &mut W,
333 panic_detector: P,
334 color_scheme: Option<&'static AnsiColorScheme>,
335 ) -> usize {
336 let _ctx = unwind_context_with_fmt!(
337 (fn(foo, bar)),
338 writer = writer1,
339 panic_detector = panic_detector.clone(),
340 color_scheme = color_scheme,
341 );
342 func2(
343 foo.checked_mul(2).unwrap(),
344 &bar[1..],
345 writer2,
346 writer3,
347 panic_detector,
348 color_scheme,
349 )
350 }
351
352 #[allow(clippy::unwrap_used)]
354 fn func2<W: FmtWrite, P: Clone + PanicDetector>(
355 foo: usize,
356 bar: &str,
357 writer2: &mut W,
358 writer3: &mut W,
359 panic_detector: P,
360 color_scheme: Option<&'static AnsiColorScheme>,
361 ) -> usize {
362 let _ctx = unwind_context_with_fmt!(
363 (fn(foo, bar)),
364 writer = writer2,
365 panic_detector = panic_detector.clone(),
366 color_scheme = color_scheme,
367 );
368 func3(
369 foo.checked_mul(3).unwrap(),
370 &bar[1..],
371 writer3,
372 panic_detector,
373 color_scheme,
374 )
375 }
376
377 #[allow(clippy::unwrap_used)]
378 fn func3<W: FmtWrite, P: PanicDetector>(
379 foo: usize,
380 bar: &str,
381 writer3: &mut W,
382 panic_detector: P,
383 color_scheme: Option<&'static AnsiColorScheme>,
384 ) -> usize {
385 let _ctx = unwind_context_with_fmt!(
386 (fn(foo, bar)),
387 writer = writer3,
388 panic_detector = panic_detector,
389 color_scheme = color_scheme,
390 );
391 foo.checked_sub(bar.len()).unwrap()
392 }
393
394 #[cfg(feature = "std")]
395 #[allow(clippy::unwrap_used)]
396 fn func_with_debug_unwind_context<W: FmtWrite, P: PanicDetector>(
397 foo: usize,
398 bar: &str,
399 #[allow(unused_variables)] writer: &mut W,
400 #[allow(unused_variables)] panic_detector: P,
401 #[allow(unused_variables)] color_scheme: Option<&'static AnsiColorScheme>,
402 ) -> usize {
403 let _ctx = debug_unwind_context_with_fmt!(
404 (fn(foo, bar)),
405 writer = writer,
406 panic_detector = panic_detector,
407 color_scheme = color_scheme,
408 );
409 foo.checked_sub(bar.len()).unwrap()
410 }
411
412 fn get_max_line() -> u32 {
415 line!()
416 }
417
418 #[allow(clippy::unwrap_used)]
419 #[test]
420 fn test_unwind_context_with_fmt_without_unwind() {
421 let is_panicking = AtomicBool::new(false);
422 let dummy_panic_detector = DummyPanicDetector {
423 is_panicking: &is_panicking,
424 };
425
426 let mut buffer1 = [0; 128];
427 let mut buffer2 = [0; 128];
428 let mut buffer3 = [0; 128];
429
430 let mut writer1 = FixedBufWriter::new(&mut buffer1);
431 let mut writer2 = FixedBufWriter::new(&mut buffer2);
432 let mut writer3 = FixedBufWriter::new(&mut buffer3);
433 let result = func1(
434 1000,
435 "abcdef",
436 &mut writer1,
437 &mut writer2,
438 &mut writer3,
439 dummy_panic_detector.clone(),
440 None,
441 );
442 assert_eq!(result, 5996);
443 assert_eq!(writer1.into_str(), "");
444 assert_eq!(writer2.into_str(), "");
445 assert_eq!(writer3.into_str(), "");
446
447 let mut writer1 = FixedBufWriter::new(&mut buffer1);
448 let mut writer2 = FixedBufWriter::new(&mut buffer2);
449 let mut writer3 = FixedBufWriter::new(&mut buffer3);
450 let result = func1(
451 1000,
452 "ab",
453 &mut writer1,
454 &mut writer2,
455 &mut writer3,
456 dummy_panic_detector.clone(),
457 None,
458 );
459 assert_eq!(result, 6000);
460 assert_eq!(writer1.into_str(), "");
461 assert_eq!(writer2.into_str(), "");
462 assert_eq!(writer3.into_str(), "");
463
464 is_panicking.store(true, AtomicOrdering::Relaxed);
467
468 let mut writer1 = FixedBufWriter::new(&mut buffer1);
469 let mut writer2 = FixedBufWriter::new(&mut buffer2);
470 let mut writer3 = FixedBufWriter::new(&mut buffer3);
471 let result = func1(
472 1000,
473 "ab",
474 &mut writer1,
475 &mut writer2,
476 &mut writer3,
477 dummy_panic_detector.clone(),
478 None,
479 );
480 assert_eq!(result, 6000);
481
482 let output = &mut writer1.into_str();
483 output
484 .expect_str("fn func1(foo: 1000, bar: \"ab\")\n")
485 .unwrap();
486 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
487 assert_eq!(*output, "");
488
489 let output = &mut writer2.into_str();
490 output
491 .expect_str("fn func2(foo: 2000, bar: \"b\")\n")
492 .unwrap();
493 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
494 assert_eq!(*output, "");
495
496 let output = &mut writer3.into_str();
497 output
498 .expect_str("fn func3(foo: 6000, bar: \"\")\n")
499 .unwrap();
500 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
501 assert_eq!(*output, "");
502 }
503
504 #[allow(clippy::unwrap_used)]
505 #[test]
506 fn test_unwind_context_with_fmt_without_unwind_with_colored_fmt() {
507 let is_panicking = AtomicBool::new(false);
508 let dummy_panic_detector = DummyPanicDetector {
509 is_panicking: &is_panicking,
510 };
511
512 let mut buffer1 = [0; 256];
513 let mut buffer2 = [0; 256];
514 let mut buffer3 = [0; 256];
515
516 is_panicking.store(true, AtomicOrdering::Relaxed);
519
520 let mut writer1 = FixedBufWriter::new(&mut buffer1);
521 let mut writer2 = FixedBufWriter::new(&mut buffer2);
522 let mut writer3 = FixedBufWriter::new(&mut buffer3);
523 let result = func1(
524 1000,
525 "ab",
526 &mut writer1,
527 &mut writer2,
528 &mut writer3,
529 dummy_panic_detector.clone(),
530 Some(&TEST_COLOR_SCHEME),
531 );
532 assert_eq!(result, 6000);
533
534 let output = &mut writer1.into_str();
535 output
536 .expect_str(
537 "{FN}fn {FN_NAME}func1{FN_BRACE}({DEF}foo: {NUM}1000{DEF}, bar: \
538 {QUOT}\"ab\"{DEF}{FN_BRACE}){DEF}\n",
539 )
540 .unwrap();
541 check_location_part(
542 output,
543 "{LOC}",
544 "{DEF}",
545 file!(),
546 get_min_line(),
547 get_max_line(),
548 );
549 assert_eq!(*output, "");
550
551 let output = &mut writer2.into_str();
552 output
553 .expect_str(
554 "{FN}fn {FN_NAME}func2{FN_BRACE}({DEF}foo: {NUM}2000{DEF}, bar: \
555 {QUOT}\"b\"{DEF}{FN_BRACE}){DEF}\n",
556 )
557 .unwrap();
558 check_location_part(
559 output,
560 "{LOC}",
561 "{DEF}",
562 file!(),
563 get_min_line(),
564 get_max_line(),
565 );
566 assert_eq!(*output, "");
567
568 let output = &mut writer3.into_str();
569 output
570 .expect_str(
571 "{FN}fn {FN_NAME}func3{FN_BRACE}({DEF}foo: {NUM}6000{DEF}, bar: \
572 {QUOT}\"\"{DEF}{FN_BRACE}){DEF}\n",
573 )
574 .unwrap();
575 check_location_part(
576 output,
577 "{LOC}",
578 "{DEF}",
579 file!(),
580 get_min_line(),
581 get_max_line(),
582 );
583 assert_eq!(*output, "");
584 }
585
586 #[cfg(feature = "std")]
587 #[allow(clippy::unwrap_used)]
588 #[test]
589 fn test_unwind_context_with_fmt_with_unwind() {
590 let panic_detector = StdPanicDetector;
591
592 let (sender, recv) = mpsc::channel();
593 let mut writer1 = ChannelWriter(sender);
594 let mut writer2 = writer1.clone();
595 let mut writer3 = writer1.clone();
596
597 let result = std::panic::catch_unwind(move || {
598 func1(
599 1000,
600 "a",
601 &mut writer1,
602 &mut writer2,
603 &mut writer3,
604 panic_detector,
605 None,
606 )
607 });
608 assert!(result.is_err());
609 let output = collect_string_from_recv(&recv);
610 let output = &mut output.as_str();
611 output
612 .expect_str("fn func2(foo: 2000, bar: \"\")\n")
613 .unwrap();
614 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
615 output
616 .expect_str("fn func1(foo: 1000, bar: \"a\")\n")
617 .unwrap();
618 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
619 assert_eq!(*output, "");
620
621 let (sender, recv) = mpsc::channel();
622 let mut writer1 = ChannelWriter(sender);
623 let mut writer2 = writer1.clone();
624 let mut writer3 = writer1.clone();
625
626 let result = std::panic::catch_unwind(move || {
627 func1(
628 1000,
629 "",
630 &mut writer1,
631 &mut writer2,
632 &mut writer3,
633 panic_detector,
634 None,
635 )
636 });
637 assert!(result.is_err());
638 let output = collect_string_from_recv(&recv);
639 let output = &mut output.as_str();
640 output
641 .expect_str("fn func1(foo: 1000, bar: \"\")\n")
642 .unwrap();
643 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
644 assert_eq!(*output, "");
645
646 let (sender, recv) = mpsc::channel();
647 let mut writer1 = ChannelWriter(sender);
648 let mut writer2 = writer1.clone();
649 let mut writer3 = writer1.clone();
650
651 let result = std::panic::catch_unwind(move || {
652 func1(
653 0,
654 "abcdef",
655 &mut writer1,
656 &mut writer2,
657 &mut writer3,
658 panic_detector,
659 None,
660 )
661 });
662 assert!(result.is_err());
663 let output = collect_string_from_recv(&recv);
664 let output = &mut output.as_str();
665 output
666 .expect_str("fn func3(foo: 0, bar: \"cdef\")\n")
667 .unwrap();
668 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
669 output
670 .expect_str("fn func2(foo: 0, bar: \"bcdef\")\n")
671 .unwrap();
672 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
673 output
674 .expect_str("fn func1(foo: 0, bar: \"abcdef\")\n")
675 .unwrap();
676 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
677 assert_eq!(*output, "");
678 }
679
680 #[cfg(feature = "std")]
681 #[allow(clippy::unwrap_used)]
682 #[test]
683 fn test_debug_unwind_context_with_io_without_unwind() {
684 let panic_detector = StdPanicDetector;
685
686 let (sender, recv) = mpsc::channel();
687 let mut writer = ChannelWriter(sender);
688
689 let result = std::panic::catch_unwind(move || {
690 func_with_debug_unwind_context(4, "abc", &mut writer, panic_detector, None)
691 });
692
693 assert_eq!(result.unwrap(), 1);
694 let output = collect_string_from_recv(&recv);
695 assert_eq!(output, "");
696 }
697
698 #[cfg(feature = "std")]
699 #[test]
700 fn test_debug_unwind_context_with_fmt_with_unwind() {
701 let panic_detector = StdPanicDetector;
702
703 let (sender, recv) = mpsc::channel();
704 let mut writer = ChannelWriter(sender);
705
706 let result = std::panic::catch_unwind(move || {
707 func_with_debug_unwind_context(2, "abc", &mut writer, panic_detector, None)
708 });
709 assert!(result.is_err());
710 let output = collect_string_from_recv(&recv);
711 let output = &mut output.as_str();
712
713 #[cfg(debug_assertions)]
714 {
715 output
716 .expect_str("fn func_with_debug_unwind_context(foo: 2, bar: \"abc\")\n")
717 .unwrap();
718 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
719 }
720 assert_eq!(*output, "");
721 }
722}