unwind_context/context_with_io.rs
1use core::fmt::Debug;
2use core::panic::Location;
3use std::io::Write;
4
5use crate::{AnsiColorScheme, AnsiColored, DebugAnsiColored, PanicDetector};
6
7/// A structure representing a scoped guard with unwind context with
8/// [`core::fmt::Write`] writer.
9///
10/// If dropped during unwind it will write a message to a given writer
11/// containing given function or scope context.
12///
13/// When this structure is dropped (falls out of scope) and the current thread
14/// is not unwinding, the unwind context will be forgotten.
15///
16/// # Examples
17///
18/// ```rust
19/// use unwind_context::{unwind_context, UnwindContextWithIo};
20///
21/// fn func(foo: u32, bar: &str, secret: &str) {
22/// let _ctx: UnwindContextWithIo<_, _, _> = unwind_context!(fn(foo, bar, ...));
23/// // ...
24/// }
25/// ```
26///
27/// [`unwind_context`]: crate::unwind_context
28#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
29pub struct UnwindContextWithIo<W: Write, T: Debug + DebugAnsiColored, P: PanicDetector> {
30 data: T,
31 writer: W,
32 panic_detector: P,
33 color_scheme: Option<&'static AnsiColorScheme>,
34 location: &'static Location<'static>,
35}
36
37impl<W: Write, T: Debug + DebugAnsiColored, P: PanicDetector> Drop
38 for UnwindContextWithIo<W, T, P>
39{
40 #[inline]
41 fn drop(&mut self) {
42 if self.panic_detector.is_panicking() {
43 self.print();
44 }
45 }
46}
47
48impl<W: Write, T: Debug + DebugAnsiColored, P: PanicDetector> UnwindContextWithIo<W, T, P> {
49 /// Create a new `UnwindContextWithFmt` with the provided
50 /// [`core::fmt::Write`] writer, context scope data, and color scheme.
51 ///
52 /// This function is not intended to be used directly. Consider using macros
53 /// like [`unwind_context`] or [`unwind_context_with_io`] instead.
54 ///
55 /// [`unwind_context`]: crate::unwind_context
56 /// [`unwind_context_with_io`]: crate::unwind_context_with_io
57 #[inline]
58 #[must_use = "\
59 if unused, the `UnwindContextWithIo` will immediately drop,
60 consider binding the `UnwindContextWithIo` like `let _ctx = ...`.
61 "]
62 #[track_caller]
63 pub fn new(
64 data: T,
65 writer: W,
66 panic_detector: P,
67 color_scheme: Option<&'static AnsiColorScheme>,
68 ) -> Self {
69 Self {
70 data,
71 writer,
72 panic_detector,
73 color_scheme,
74 location: Location::caller(),
75 }
76 }
77
78 /// Print context to a writer specified in the `UnwindContextWithIo`
79 /// constructor.
80 ///
81 /// This method is called when a panic detected.
82 #[cold]
83 #[inline(never)]
84 pub fn print(&mut self) {
85 if let Some(color_scheme) = self.color_scheme {
86 let _ = writeln!(
87 self.writer,
88 "{:?}\n at {}{}:{}:{}{}",
89 AnsiColored::new(&self.data, color_scheme),
90 color_scheme.location,
91 self.location.file(),
92 self.location.line(),
93 self.location.column(),
94 color_scheme.default,
95 );
96 } else {
97 let _ = writeln!(
98 self.writer,
99 "{:?}\n at {}:{}:{}",
100 self.data,
101 self.location.file(),
102 self.location.line(),
103 self.location.column(),
104 );
105 }
106 let _ = self.writer.flush();
107 }
108}
109
110/// Creates [`UnwindContextWithIo`] with a given [`std::io::Write`] writer,
111/// panic detector, color scheme, and a given function or scope context.
112///
113/// If not specified it uses [`std::io::stderr`] as a default writer,
114/// [`StdPanicDetector`] as a default panic detector and
115/// [`get_default_color_scheme_if_enabled`] as a default color scheme. When
116/// using default values for all optional parameters, consider the
117/// use of [`unwind_context`] macro instead. See
118/// [equivalent macros](#equivalent-macros) section below.
119///
120/// The returned unwind context scope guard value should be kept alive as long
121/// as unwind context is needed. If unused, the [`UnwindContextWithIo`] will
122/// immediately drop.
123///
124/// Passed context arguments are lazily formatted. The created wrapper takes
125/// ownership of the given arguments, so it may be necessary to use value
126/// references, clones, or pass the pre-prepared string representation. It also
127/// supports the `...` placeholder to show that some values have been omitted.
128///
129/// For more information about context argument, see
130/// [`build_unwind_context_data`].
131///
132/// # Examples
133///
134/// ```rust
135/// use unwind_context::unwind_context_with_io;
136///
137/// fn example1(foo: u32, bar: &str, secret: &str) {
138/// let _ctx = unwind_context_with_io!((fn(foo, bar, ...)), color_scheme = None);
139/// // ...
140/// }
141/// ```
142///
143/// ```rust
144/// use unwind_context::unwind_context_with_io;
145///
146/// fn example2(foo: u32, bar: &str, secret: &str) {
147/// let _ctx = unwind_context_with_io!((fn(foo, bar, ...)), writer = ::std::io::stdout());
148/// // ...
149/// }
150/// ```
151///
152/// ```rust
153/// use unwind_context::{unwind_context_with_io, AnsiColorScheme};
154///
155/// fn example3<W: std::io::Write, P: unwind_context::PanicDetector>(
156/// foo: u32,
157/// bar: &str,
158/// custom_writer: &mut W,
159/// custom_panic_detector: P,
160/// custom_color_scheme: &'static AnsiColorScheme,
161/// ) {
162/// let _ctx = unwind_context_with_io!(
163/// (fn(foo, bar)),
164/// writer = custom_writer,
165/// panic_detector = custom_panic_detector,
166/// color_scheme = Some(custom_color_scheme),
167/// );
168/// // ...
169/// }
170/// ```
171///
172/// # Equivalent macros
173/// ```rust
174/// use unwind_context::{unwind_context, unwind_context_with_io};
175///
176/// fn func(foo: u32, bar: &str) {
177/// let _ctx = unwind_context!(fn(foo, bar));
178/// let _ctx = unwind_context_with_io!((fn(foo, bar)));
179/// let _ctx = unwind_context_with_io!(
180/// (fn(foo, bar)),
181/// writer = ::std::io::stderr(),
182/// panic_detector = unwind_context::StdPanicDetector,
183/// color_scheme = unwind_context::get_default_color_scheme_if_enabled(),
184/// );
185/// }
186/// ```
187///
188/// [`unwind_context`]: crate::unwind_context
189/// [`StdPanicDetector`]: crate::StdPanicDetector
190/// [`get_default_color_scheme_if_enabled`]: crate::get_default_color_scheme_if_enabled
191/// [`build_unwind_context_data`]: crate::build_unwind_context_data
192#[macro_export]
193macro_rules! unwind_context_with_io {
194 (
195 ( $( $context:tt )* )
196 $(, writer = $writer:expr )?
197 $(, panic_detector = $panic_detector:expr )?
198 $(, color_scheme = $color_scheme:expr )?
199 $(,)?
200 ) => {
201 $crate::UnwindContextWithIo::new(
202 $crate::build_unwind_context_data!( $($context)* ),
203 $crate::expr_or_default_expr!(
204 $( $writer )?,
205 ::std::io::stderr()
206 ),
207 $crate::expr_or_default_expr!(
208 $( $panic_detector )?,
209 $crate::StdPanicDetector
210 ),
211 $crate::expr_or_default_expr!(
212 $( $color_scheme )?,
213 $crate::get_default_color_scheme_if_enabled()
214 ),
215 )
216 };
217}
218
219/// Creates [`UnwindContextWithIo`] with a given [`std::io::Write`] writer,
220/// panic detector, color scheme, and a given function or scope context in debug
221/// builds only.
222///
223/// If not specified it uses [`std::io::stderr`] as a default writer,
224/// [`StdPanicDetector`] as a default panic detector and
225/// [`get_default_color_scheme_if_enabled`] as a default color scheme. When
226/// using default values for all optional parameters, consider the
227/// use of [`debug_unwind_context`] macro instead. See
228/// [equivalent macros](#equivalent-macros) section below.
229///
230/// The returned unwind context scope guard value should be kept alive as long
231/// as unwind context is needed. If unused, the [`UnwindContextWithIo`] will
232/// immediately drop.
233///
234/// Passed context arguments are lazily formatted. The created wrapper takes
235/// ownership of the given arguments, so it may be necessary to use value
236/// references, clones, or pass the pre-prepared string representation. It also
237/// supports the `...` placeholder to show that some values have been omitted.
238///
239/// An optimized build will generate `()` unless `-C debug-assertions` is passed
240/// to the compiler. This makes this macro no-op with the default release
241/// profile.
242///
243/// For more information about macro arguments, see [`unwind_context_with_io`].
244/// For more information about context argument, see
245/// [`build_unwind_context_data`].
246///
247/// # Examples
248///
249/// ```rust
250/// use unwind_context::debug_unwind_context_with_io;
251///
252/// fn example1(foo: u32, bar: &str, secret: &str) {
253/// let _ctx = debug_unwind_context_with_io!((fn(foo, bar, ...)), color_scheme = None);
254/// // ...
255/// }
256/// ```
257///
258/// ```rust
259/// use unwind_context::debug_unwind_context_with_io;
260///
261/// fn example2(foo: u32, bar: &str, secret: &str) {
262/// let _ctx = debug_unwind_context_with_io!((fn(foo, bar, ...)), writer = ::std::io::stdout());
263/// // ...
264/// }
265/// ```
266///
267/// ```rust
268/// use unwind_context::{debug_unwind_context_with_io, AnsiColorScheme};
269///
270/// fn example3<W: std::io::Write, P: unwind_context::PanicDetector>(
271/// foo: u32,
272/// bar: &str,
273/// custom_writer: &mut W,
274/// custom_panic_detector: P,
275/// custom_color_scheme: &'static AnsiColorScheme,
276/// ) {
277/// let _ctx = debug_unwind_context_with_io!(
278/// (fn(foo, bar)),
279/// writer = custom_writer,
280/// panic_detector = custom_panic_detector,
281/// color_scheme = Some(custom_color_scheme),
282/// );
283/// // ...
284/// }
285/// ```
286///
287/// # Equivalent macros
288/// ```rust
289/// use unwind_context::{debug_unwind_context, debug_unwind_context_with_io};
290///
291/// fn func(foo: u32, bar: &str) {
292/// debug_unwind_context!(fn(foo, bar));
293/// debug_unwind_context_with_io!((fn(foo, bar)));
294/// debug_unwind_context_with_io!(
295/// (fn(foo, bar)),
296/// writer = ::std::io::stderr(),
297/// panic_detector = unwind_context::StdPanicDetector,
298/// color_scheme = unwind_context::get_default_color_scheme_if_enabled(),
299/// );
300/// }
301/// ```
302///
303/// [`unwind_context_with_io`]: crate::unwind_context_with_io
304/// [`debug_unwind_context`]: crate::debug_unwind_context
305/// [`StdPanicDetector`]: crate::StdPanicDetector
306/// [`get_default_color_scheme_if_enabled`]: crate::get_default_color_scheme_if_enabled
307/// [`build_unwind_context_data`]: crate::build_unwind_context_data
308#[macro_export]
309macro_rules! debug_unwind_context_with_io {
310 ( $( $tokens:tt )* ) => { $crate::debug_unwind_context_with_io_impl!( $($tokens)* ) };
311}
312
313#[doc(hidden)]
314#[cfg(debug_assertions)]
315#[macro_export]
316macro_rules! debug_unwind_context_with_io_impl {
317 ( $( $tokens:tt )* ) => { $crate::unwind_context_with_io!( $($tokens)* ) };
318}
319
320#[doc(hidden)]
321#[cfg(not(debug_assertions))]
322#[macro_export]
323macro_rules! debug_unwind_context_with_io_impl {
324 ($($tokens:tt)*) => {
325 ()
326 };
327}
328
329#[cfg(test)]
330mod tests {
331 use std::borrow::ToOwned;
332 use std::io::{Result as IoResult, Write as IoWrite};
333 use std::string::String;
334 use std::sync::mpsc;
335
336 use crate::test_common::{check_location_part, TEST_COLOR_SCHEME};
337 use crate::test_util::{collect_string_from_recv, PatternMatcher};
338 use crate::AnsiColorScheme;
339
340 #[derive(Clone)]
341 pub struct Writer(mpsc::Sender<String>);
342
343 impl IoWrite for Writer {
344 #[allow(clippy::unwrap_used)]
345 fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
346 self.0
347 .send(String::from_utf8(buf.to_owned()).unwrap())
348 .unwrap();
349 Ok(buf.len())
350 }
351
352 fn flush(&mut self) -> IoResult<()> {
353 Ok(())
354 }
355 }
356
357 fn get_min_line() -> u32 {
358 line!()
359 }
360
361 #[allow(clippy::unwrap_used)]
362 fn func1<W: Clone + IoWrite>(
363 foo: usize,
364 bar: &str,
365 writer: &mut W,
366 color_scheme: Option<&'static AnsiColorScheme>,
367 ) -> usize {
368 let _ctx = unwind_context_with_io!(
369 (fn(foo, bar)),
370 writer = writer.clone(),
371 color_scheme = color_scheme
372 );
373 func2(foo.checked_mul(2).unwrap(), &bar[1..], writer, color_scheme)
374 }
375
376 #[allow(clippy::unwrap_used)]
377 fn func2<W: Clone + IoWrite>(
378 foo: usize,
379 bar: &str,
380 writer: &mut W,
381 color_scheme: Option<&'static AnsiColorScheme>,
382 ) -> usize {
383 let _ctx = unwind_context_with_io!(
384 (fn(foo, bar)),
385 writer = writer.clone(),
386 color_scheme = color_scheme
387 );
388 func3(foo.checked_mul(3).unwrap(), &bar[1..], writer, color_scheme)
389 }
390
391 #[allow(clippy::unwrap_used)]
392 fn func3<W: IoWrite>(
393 foo: usize,
394 bar: &str,
395 writer: &mut W,
396 color_scheme: Option<&'static AnsiColorScheme>,
397 ) -> usize {
398 let _ctx =
399 unwind_context_with_io!((fn(foo, bar)), writer = writer, color_scheme = color_scheme);
400 foo.checked_sub(bar.len()).unwrap()
401 }
402
403 #[allow(clippy::unwrap_used)]
404 fn func_with_debug_unwind_context<W: IoWrite>(
405 foo: usize,
406 bar: &str,
407 #[allow(unused_variables)] writer: &mut W,
408 #[allow(unused_variables)] color_scheme: Option<&'static AnsiColorScheme>,
409 ) -> usize {
410 let _ctx = debug_unwind_context_with_io!(
411 (fn(foo, bar)),
412 writer = writer,
413 color_scheme = color_scheme
414 );
415 foo.checked_sub(bar.len()).unwrap()
416 }
417
418 fn get_max_line() -> u32 {
419 line!()
420 }
421
422 #[allow(clippy::unwrap_used)]
423 #[test]
424 fn test_unwind_context_with_io_without_unwind() {
425 let (sender, recv) = mpsc::channel();
426 let mut writer = Writer(sender);
427 let result = func1(1000, "abcdef", &mut writer, None);
428 assert_eq!(result, 5996);
429 assert_eq!(collect_string_from_recv(&recv), "");
430
431 let (sender, recv) = mpsc::channel();
432 let mut writer = Writer(sender);
433 let result = func1(1000, "ab", &mut writer, None);
434 assert_eq!(result, 6000);
435 assert_eq!(collect_string_from_recv(&recv), "");
436 }
437
438 #[allow(clippy::unwrap_used)]
439 #[test]
440 fn test_unwind_context_with_io_with_unwind() {
441 let (sender, recv) = mpsc::channel();
442 let mut writer = Writer(sender);
443 let result = std::panic::catch_unwind(move || func1(1000, "a", &mut writer, None));
444 assert!(result.is_err());
445 let output = collect_string_from_recv(&recv);
446 let output = &mut output.as_str();
447 output
448 .expect_str("fn func2(foo: 2000, bar: \"\")\n")
449 .unwrap();
450 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
451 output
452 .expect_str("fn func1(foo: 1000, bar: \"a\")\n")
453 .unwrap();
454 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
455 assert_eq!(*output, "");
456
457 let (sender, recv) = mpsc::channel();
458 let mut writer = Writer(sender);
459 let result = std::panic::catch_unwind(move || func1(1000, "", &mut writer, None));
460 assert!(result.is_err());
461 let output = collect_string_from_recv(&recv);
462 let output = &mut output.as_str();
463 output
464 .expect_str("fn func1(foo: 1000, bar: \"\")\n")
465 .unwrap();
466 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
467 assert_eq!(*output, "");
468
469 let (sender, recv) = mpsc::channel();
470 let mut writer = Writer(sender);
471 let result = std::panic::catch_unwind(move || func1(0, "abcdef", &mut writer, None));
472 assert!(result.is_err());
473 let output = collect_string_from_recv(&recv);
474 let output = &mut output.as_str();
475 output
476 .expect_str("fn func3(foo: 0, bar: \"cdef\")\n")
477 .unwrap();
478 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
479 output
480 .expect_str("fn func2(foo: 0, bar: \"bcdef\")\n")
481 .unwrap();
482 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
483 output
484 .expect_str("fn func1(foo: 0, bar: \"abcdef\")\n")
485 .unwrap();
486 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
487 assert_eq!(*output, "");
488 }
489
490 #[allow(clippy::unwrap_used)]
491 #[test]
492 fn test_unwind_context_with_io_with_unwind_with_colored_fmt() {
493 let (sender, recv) = mpsc::channel();
494 let mut writer = Writer(sender);
495 let result = std::panic::catch_unwind(move || {
496 func1(1000, "a", &mut writer, Some(&TEST_COLOR_SCHEME))
497 });
498 assert!(result.is_err());
499 let output = collect_string_from_recv(&recv);
500 let output = &mut output.as_str();
501 output
502 .expect_str(
503 "{FN}fn {FN_NAME}func2{FN_BRACE}({DEF}foo: {NUM}2000{DEF}, bar: \
504 {QUOT}\"\"{DEF}{FN_BRACE}){DEF}\n",
505 )
506 .unwrap();
507 check_location_part(
508 output,
509 "{LOC}",
510 "{DEF}",
511 file!(),
512 get_min_line(),
513 get_max_line(),
514 );
515 output
516 .expect_str(
517 "{FN}fn {FN_NAME}func1{FN_BRACE}({DEF}foo: {NUM}1000{DEF}, bar: \
518 {QUOT}\"a\"{DEF}{FN_BRACE}){DEF}\n",
519 )
520 .unwrap();
521 check_location_part(
522 output,
523 "{LOC}",
524 "{DEF}",
525 file!(),
526 get_min_line(),
527 get_max_line(),
528 );
529 assert_eq!(*output, "");
530 }
531
532 #[allow(clippy::unwrap_used)]
533 #[test]
534 fn test_debug_unwind_context_with_io_without_unwind() {
535 let (sender, recv) = mpsc::channel();
536 let mut writer = Writer(sender);
537 let result = std::panic::catch_unwind(move || {
538 func_with_debug_unwind_context(4, "abc", &mut writer, None)
539 });
540
541 assert_eq!(result.unwrap(), 1);
542 let output = collect_string_from_recv(&recv);
543 assert_eq!(output, "");
544 }
545
546 #[test]
547 fn test_debug_unwind_context_with_io_with_unwind() {
548 let (sender, recv) = mpsc::channel();
549 let mut writer = Writer(sender);
550 let result = std::panic::catch_unwind(move || {
551 func_with_debug_unwind_context(2, "abc", &mut writer, None)
552 });
553 assert!(result.is_err());
554 let output = collect_string_from_recv(&recv);
555 let output = &mut output.as_str();
556
557 #[cfg(debug_assertions)]
558 {
559 output
560 .expect_str("fn func_with_debug_unwind_context(foo: 2, bar: \"abc\")\n")
561 .unwrap();
562 check_location_part(output, "", "", file!(), get_min_line(), get_max_line());
563 assert_eq!(*output, "");
564 }
565 assert_eq!(*output, "");
566 }
567}