diff --git a/Cargo.toml b/Cargo.toml index e11d9bd..dba6201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,4 @@ rust-version = "1.65.0" indenter = "0.3.0" once_cell = "1.18.0" # For use with anyhow-compat -anyhow = "1.0" +anyhow = { version = "1.0", features = ["backtrace"] } diff --git a/eyre/examples/custom_handler.rs b/eyre/examples/custom_handler.rs index a6f0ab2..e4aaae7 100644 --- a/eyre/examples/custom_handler.rs +++ b/eyre/examples/custom_handler.rs @@ -27,7 +27,9 @@ fn install() -> Result<(), impl Error> { let hook = Hook { capture_backtrace }; - eyre::set_hook(Box::new(move |e| Box::new(hook.make_handler(e)))) + eyre::set_hook(Box::new(move |e: &(dyn Error + 'static)| { + Box::new(hook.make_handler(e)) as Box + })) } struct Hook { diff --git a/eyre/src/backtrace.rs b/eyre/src/backtrace.rs index 5899133..7474cd0 100644 --- a/eyre/src/backtrace.rs +++ b/eyre/src/backtrace.rs @@ -10,7 +10,7 @@ macro_rules! backtrace_if_absent { ($err:expr) => { match $err.backtrace() { Some(_) => None, - None => Some(Backtrace::capture()), + None => Some(Backtrace::capture().into()), } }; } diff --git a/eyre/src/builder.rs b/eyre/src/builder.rs index 0e6ab26..8e1fee9 100644 --- a/eyre/src/builder.rs +++ b/eyre/src/builder.rs @@ -1,38 +1,34 @@ use crate::{ - backtrace::Backtrace, + error::{context_downcast, context_downcast_mut, context_drop_rest, ContextError}, vtable::{ object_boxed, object_downcast, object_downcast_mut, object_drop, object_drop_front, object_mut, object_ref, ErrorVTable, }, - Report, StdError, + HandlerBacktraceCompat, HookParams, Report, StdError, }; use std::fmt::Display; -#[derive(Default)] +#[derive(Debug, Default)] +/// Used for incrementally constructing reports pub struct ReportBuilder { - backtrace: Option, -} - -impl std::fmt::Debug for ReportBuilder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() - } + params: HookParams, } impl ReportBuilder { + /// Creates a new report builder with default parameters pub fn new() -> Self { Self::default() } /// Use the given backtrace for the error - pub fn with_backtrace(mut self, backtrace: Option) -> Self { - self.backtrace = backtrace; + pub fn with_backtrace(mut self, backtrace: impl Into) -> Self { + self.params.backtrace = Some(backtrace.into()); self } #[cfg_attr(track_caller, track_caller)] /// Creates a report from the given error message - pub fn msg(self, message: M) -> Report + pub fn from_msg(self, message: M) -> Report where M: Display + std::fmt::Debug + Send + Sync + 'static, { @@ -50,14 +46,14 @@ impl ReportBuilder { // Safety: MessageError is repr(transparent) so it is okay for the // vtable to allow casting the MessageError to M. - let handler = Some(crate::capture_handler(&error)); + let handler = Some(crate::capture_handler(&error, self.params)); unsafe { Report::construct(error, vtable, handler) } } #[cfg_attr(track_caller, track_caller)] /// Creates a report from the following error - pub fn report(self, error: E) -> Report + pub fn from_stderr(self, error: E) -> Report where E: StdError + Send + Sync + 'static, { @@ -72,7 +68,78 @@ impl ReportBuilder { }; // Safety: passing vtable that operates on the right type E. - let handler = Some(crate::capture_handler(&error)); + let handler = Some(crate::capture_handler(&error, self.params)); + + unsafe { Report::construct(error, vtable, handler) } + } + + #[cfg_attr(track_caller, track_caller)] + /// Creates a report from the following boxed error + pub fn from_boxed(self, error: Box) -> Report { + use crate::wrapper::BoxedError; + let error = BoxedError(error); + let handler = Some(crate::capture_handler(&error, self.params)); + + let vtable = &ErrorVTable { + object_drop: object_drop::, + object_ref: object_ref::, + object_mut: object_mut::, + object_boxed: object_boxed::, + object_downcast: object_downcast::>, + object_downcast_mut: object_downcast_mut::>, + object_drop_rest: object_drop_front::>, + }; + + // Safety: BoxedError is repr(transparent) so it is okay for the vtable + // to allow casting to Box. + unsafe { Report::construct(error, vtable, handler) } + } + + #[cfg_attr(track_caller, track_caller)] + /// Wraps a source error with a message + pub fn wrap_with_msg(self, msg: D, error: E) -> Report + where + D: Display + Send + Sync + 'static, + E: StdError + Send + Sync + 'static, + { + let error: ContextError = ContextError { msg, error }; + + let vtable = &ErrorVTable { + object_drop: object_drop::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_downcast: context_downcast::, + object_downcast_mut: context_downcast_mut::, + object_drop_rest: context_drop_rest::, + }; + + // Safety: passing vtable that operates on the right type. + let handler = Some(crate::capture_handler(&error, self.params)); + + unsafe { Report::construct(error, vtable, handler) } + } + + #[cfg_attr(track_caller, track_caller)] + pub(crate) fn from_display(self, message: M) -> Report + where + M: Display + Send + Sync + 'static, + { + use crate::wrapper::{DisplayError, NoneError}; + let error: DisplayError = DisplayError(message); + let vtable = &ErrorVTable { + object_drop: object_drop::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_downcast: object_downcast::, + object_downcast_mut: object_downcast_mut::, + object_drop_rest: object_drop_front::, + }; + + // Safety: DisplayError is repr(transparent) so it is okay for the + // vtable to allow casting the DisplayError to M. + let handler = Some(crate::capture_handler(&NoneError, Default::default())); unsafe { Report::construct(error, vtable, handler) } } diff --git a/eyre/src/compat.rs b/eyre/src/compat.rs index 8d7b513..c9850fa 100644 --- a/eyre/src/compat.rs +++ b/eyre/src/compat.rs @@ -39,40 +39,10 @@ impl IntoEyreReport for anyhow::Error { where Self: Sized, { - // dbg!( - // self.root_cause(), - // self.source(), - // self.chain().rev().collect::>(), - // self.chain() - // .rev() - // .map(|v| v.to_string()) - // .collect::>() - // ); - - let mut chain = self.chain().rev(); - - // We can't store the actual error - // PENDING: https://github.com/dtolnay/anyhow/issues/327 - let head = chain - .next() - .expect("Error chain contains at least one error"); - - #[cfg(backtrace)] let report = ReportBuilder::default() .with_backtrace(self.backtrace()) - .msg(head.to_string()); - - #[cfg(not(backtrace))] - let report = ReportBuilder::default().msg(head.to_string()); - // chai - // eprintln!("{:?}", chain.map(|v| v.to_string()).collect::>()); - - // report + .from_boxed(self.into()); - chain.fold(report, |report, err| { - // We can't write the actual error - // PENDING: https://github.com/dtolnay/anyhow/issues/327 - report.wrap_err(err.to_string()) - }) + report } } diff --git a/eyre/src/error.rs b/eyre/src/error.rs index 3632778..07bb1f3 100644 --- a/eyre/src/error.rs +++ b/eyre/src/error.rs @@ -2,8 +2,8 @@ use crate::builder::ReportBuilder; use crate::chain::Chain; use crate::ptr::{MutPtr, OwnedPtr, RefPtr}; use crate::vtable::{ - header, header_mut, object_boxed, object_downcast, object_downcast_mut, object_drop, - object_drop_front, object_mut, object_ref, ErrorHeader, ErrorImpl, ErrorVTable, + header, header_mut, object_boxed, object_drop, object_mut, object_ref, ErrorHeader, ErrorImpl, + ErrorVTable, }; use crate::EyreHandler; use crate::{Report, StdError}; @@ -27,7 +27,7 @@ impl Report { where E: StdError + Send + Sync + 'static, { - ReportBuilder::default().report(error) + ReportBuilder::default().from_stderr(error) } /// Create a new error object from a printable error message. @@ -72,7 +72,7 @@ impl Report { where M: Display + Debug + Send + Sync + 'static, { - ReportBuilder::default().msg(message) + ReportBuilder::default().from_msg(message) } #[cfg_attr(track_caller, track_caller)] @@ -80,7 +80,7 @@ impl Report { where M: Display + Debug + Send + Sync + 'static, { - ReportBuilder::default().msg(message) + ReportBuilder::default().from_msg(message) } #[cfg_attr(track_caller, track_caller)] @@ -88,23 +88,7 @@ impl Report { where M: Display + Send + Sync + 'static, { - use crate::wrapper::{DisplayError, NoneError}; - let error: DisplayError = DisplayError(message); - let vtable = &ErrorVTable { - object_drop: object_drop::>, - object_ref: object_ref::>, - object_mut: object_mut::>, - object_boxed: object_boxed::>, - object_downcast: object_downcast::, - object_downcast_mut: object_downcast_mut::, - object_drop_rest: object_drop_front::, - }; - - // Safety: DisplayError is repr(transparent) so it is okay for the - // vtable to allow casting the DisplayError to M. - let handler = Some(crate::capture_handler(&NoneError)); - - unsafe { Report::construct(error, vtable, handler) } + ReportBuilder::default().from_display(message) } #[cfg_attr(track_caller, track_caller)] @@ -114,43 +98,7 @@ impl Report { D: Display + Send + Sync + 'static, E: StdError + Send + Sync + 'static, { - let error: ContextError = ContextError { msg, error }; - - let vtable = &ErrorVTable { - object_drop: object_drop::>, - object_ref: object_ref::>, - object_mut: object_mut::>, - object_boxed: object_boxed::>, - object_downcast: context_downcast::, - object_downcast_mut: context_downcast_mut::, - object_drop_rest: context_drop_rest::, - }; - - // Safety: passing vtable that operates on the right type. - let handler = Some(crate::capture_handler(&error)); - - unsafe { Report::construct(error, vtable, handler) } - } - - #[cfg_attr(track_caller, track_caller)] - pub(crate) fn from_boxed(error: Box) -> Self { - use crate::wrapper::BoxedError; - let error = BoxedError(error); - let handler = Some(crate::capture_handler(&error)); - - let vtable = &ErrorVTable { - object_drop: object_drop::, - object_ref: object_ref::, - object_mut: object_mut::, - object_boxed: object_boxed::, - object_downcast: object_downcast::>, - object_downcast_mut: object_downcast_mut::>, - object_drop_rest: object_drop_front::>, - }; - - // Safety: BoxedError is repr(transparent) so it is okay for the vtable - // to allow casting to Box. - unsafe { Report::construct(error, vtable, handler) } + ReportBuilder::default().wrap_with_msg(msg, error) } // Takes backtrace as argument rather than capturing it here so that the @@ -461,7 +409,7 @@ where { #[cfg_attr(track_caller, track_caller)] fn from(error: E) -> Self { - ReportBuilder::default().report(error) + ReportBuilder::default().from_stderr(error) } } @@ -503,7 +451,7 @@ impl Drop for Report { /// # Safety /// /// Requires layout of *e to match ErrorImpl>. -unsafe fn context_downcast( +pub(crate) unsafe fn context_downcast( e: RefPtr<'_, ErrorImpl<()>>, target: TypeId, ) -> Option> @@ -527,7 +475,7 @@ where /// # Safety /// /// Requires layout of *e to match ErrorImpl>. -unsafe fn context_downcast_mut( +pub(crate) unsafe fn context_downcast_mut( e: MutPtr<'_, ErrorImpl<()>>, target: TypeId, ) -> Option> @@ -550,7 +498,7 @@ where /// # Safety /// /// Requires layout of *e to match ErrorImpl>. -unsafe fn context_drop_rest(e: OwnedPtr>, target: TypeId) +pub(crate) unsafe fn context_drop_rest(e: OwnedPtr>, target: TypeId) where D: 'static, E: 'static, @@ -595,7 +543,7 @@ where /// # Safety /// /// Requires layout of *e to match ErrorImpl>. -unsafe fn context_chain_downcast_mut( +pub(crate) unsafe fn context_chain_downcast_mut( e: MutPtr<'_, ErrorImpl<()>>, target: TypeId, ) -> Option> @@ -616,7 +564,7 @@ where /// # Safety /// /// Requires layout of *e to match ErrorImpl>. -unsafe fn context_chain_drop_rest(e: OwnedPtr>, target: TypeId) +pub(crate) unsafe fn context_chain_drop_rest(e: OwnedPtr>, target: TypeId) where D: 'static, { diff --git a/eyre/src/kind.rs b/eyre/src/kind.rs index bb7c5c0..0c6e2ec 100644 --- a/eyre/src/kind.rs +++ b/eyre/src/kind.rs @@ -45,7 +45,7 @@ // let error = $msg; // (&error).eyre_kind().new(error) -use crate::Report; +use crate::{builder::ReportBuilder, Report}; use core::fmt::{Debug, Display}; use crate::StdError; @@ -106,6 +106,6 @@ impl BoxedKind for Box {} impl Boxed { #[cfg_attr(track_caller, track_caller)] pub fn new(self, error: Box) -> Report { - Report::from_boxed(error) + ReportBuilder::default().from_boxed(error) } } diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index aeb14cb..1fb8efb 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -362,13 +362,13 @@ mod macros; mod ptr; mod wrapper; +/// Incrementally construct reports pub mod builder; /// Compatibility traits for conversion between different error providers in a structural /// manner. pub mod compat; mod vtable; -use crate::backtrace::Backtrace; use core::fmt::Display; use std::error::Error as StdError; @@ -468,6 +468,61 @@ pub struct Report { inner: OwnedPtr>, } +/// Provide an explicit backtrace for an error +pub enum HandlerBacktraceCompat { + /// std::backtrace::Backtrace + StdBacktrace(std::backtrace::Backtrace), + /// stable [`backtrace`](::backtrace) + Backtrace(backtrace::Backtrace), + /// An opaque backtrack + Display(Box), +} + +impl From> for HandlerBacktraceCompat { + fn from(v: Box) -> Self { + Self::Display(v) + } +} + +impl From<&T> for HandlerBacktraceCompat +where + T: Display, +{ + fn from(v: &T) -> Self { + Self::Display(Box::new(v.to_string())) + } +} + +impl From for HandlerBacktraceCompat { + fn from(v: backtrace::Backtrace) -> Self { + Self::Backtrace(v) + } +} + +impl From for HandlerBacktraceCompat { + fn from(v: std::backtrace::Backtrace) -> Self { + Self::StdBacktrace(v) + } +} + +impl std::fmt::Debug for HandlerBacktraceCompat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HandlerBacktraceCompat::StdBacktrace(_) => f.debug_tuple("StdBacktrace").finish(), + HandlerBacktraceCompat::Backtrace(_) => f.debug_tuple("Backtrace").finish(), + HandlerBacktraceCompat::Display(_) => f.debug_tuple("Display").finish(), + } + } +} + +#[derive(Debug, Default)] +#[non_exhaustive] +/// Used for configuring error generation +pub struct HookParams { + /// An explicit backtrace to attach to the error, if any + pub backtrace: Option, +} + type ErrorHook = Box Box + Sync + Send + 'static>; @@ -587,7 +642,7 @@ pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { #[cfg_attr(track_caller, track_caller)] #[cfg_attr(not(track_caller), allow(unused_mut))] -fn capture_handler(error: &(dyn StdError + 'static)) -> Box { +fn capture_handler(error: &(dyn StdError + 'static), params: HookParams) -> Box { #[cfg(not(feature = "auto-install"))] let hook = HOOK .get() @@ -601,6 +656,10 @@ fn capture_handler(error: &(dyn StdError + 'static)) -> Box { let mut handler = hook(error); + if let Some(backtrace) = params.backtrace { + handler.set_backtrace(backtrace); + } + #[cfg(track_caller)] { handler.track_caller(std::panic::Location::caller()) @@ -721,6 +780,10 @@ pub trait EyreHandler: core::any::Any + Send + Sync { /// Store the location of the caller who constructed this error report #[allow(unused_variables)] fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {} + + /// Provide an explicit backtrace for this error + #[allow(unused_variables)] + fn set_backtrace(&mut self, backtrace: HandlerBacktraceCompat) {} } /// The default provided error report handler for `eyre::Report`. @@ -729,7 +792,7 @@ pub trait EyreHandler: core::any::Any + Send + Sync { /// error did not already capture one. #[allow(dead_code)] pub struct DefaultHandler { - backtrace: Option, + backtrace: Option, #[cfg(track_caller)] location: Option<&'static std::panic::Location<'static>>, } @@ -832,6 +895,7 @@ impl EyreHandler for DefaultHandler { .as_ref() .or_else(|| error.backtrace()) .expect("backtrace capture failed"); + if let BacktraceStatus::Captured = backtrace.status() { write!(f, "\n\nStack backtrace:\n{}", backtrace)?; } @@ -844,6 +908,10 @@ impl EyreHandler for DefaultHandler { fn track_caller(&mut self, location: &'static std::panic::Location<'static>) { self.location = Some(location); } + + fn set_backtrace(&mut self, backtrace: HandlerBacktraceCompat) { + self.backtrace = Some(backtrace); + } } /// Iterator of a chain of source errors. diff --git a/eyre/src/vtable.rs b/eyre/src/vtable.rs index b4ed14c..03b8673 100644 --- a/eyre/src/vtable.rs +++ b/eyre/src/vtable.rs @@ -7,7 +7,7 @@ use std::{ use crate::{ ptr::{MutPtr, OwnedPtr, RefPtr}, - Chain, EyreContext, EyreHandler, Report, StdError, + Chain, EyreHandler, Report, StdError, }; pub(crate) struct ErrorVTable { diff --git a/eyre/tests/test_anyhow_compat.rs b/eyre/tests/test_anyhow_compat.rs index 7a0f604..7e951d0 100644 --- a/eyre/tests/test_anyhow_compat.rs +++ b/eyre/tests/test_anyhow_compat.rs @@ -28,10 +28,9 @@ fn this_function_fails() -> anyhow::Result<()> { fn bubble() -> eyre::Result<()> { use anyhow::Context; use eyre::WrapErr; - this_function_fails() - .context("Anyhow context B") - .into_eyre() - .wrap_err("Eyre context A")?; + let err = this_function_fails().context("Anyhow context B"); + + err.into_eyre().wrap_err("Eyre context A")?; Ok(()) } @@ -42,7 +41,7 @@ fn anyhow_conversion() { use eyre::WrapErr; let error: Report = bubble().wrap_err("Eyre context B").unwrap_err(); - eprintln!("{error:?}"); + eprintln!("Error: {:?}", error); let chain = error.chain().map(ToString::to_string).collect::>(); assert_eq!( diff --git a/eyre/tests/test_location.rs b/eyre/tests/test_location.rs index 58737fe..5aea404 100644 --- a/eyre/tests/test_location.rs +++ b/eyre/tests/test_location.rs @@ -1,4 +1,4 @@ -use std::panic::Location; +use std::{error::Error, panic::Location}; use eyre::WrapErr; @@ -43,7 +43,7 @@ impl eyre::EyreHandler for LocationHandler { fn test_wrap_err() { let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); let err = read_path("totally_fake_path") @@ -70,9 +70,9 @@ fn read_path(_path: &str) -> Result { #[test] fn test_wrap_err_with() { - let _ = eyre::set_hook(Box::new(|_e| { + let _ = eyre::set_hook(Box::new(|_e: &(dyn Error + 'static)| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); let err = read_path("totally_fake_path") @@ -85,9 +85,9 @@ fn test_wrap_err_with() { #[test] fn test_context() { - let _ = eyre::set_hook(Box::new(|_e| { + let _ = eyre::set_hook(Box::new(|_e: &(dyn Error + 'static)| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); let err = read_path("totally_fake_path") @@ -102,7 +102,7 @@ fn test_context() { fn test_with_context() { let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); let err = read_path("totally_fake_path") @@ -117,7 +117,7 @@ fn test_with_context() { fn test_option_compat_wrap_err() { let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); use eyre::ContextCompat; @@ -131,7 +131,7 @@ fn test_option_compat_wrap_err() { fn test_option_compat_wrap_err_with() { let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); use eyre::ContextCompat; @@ -145,7 +145,7 @@ fn test_option_compat_wrap_err_with() { fn test_option_compat_context() { let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); use eyre::ContextCompat; @@ -159,7 +159,7 @@ fn test_option_compat_context() { fn test_option_compat_with_context() { let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) + Box::new(LocationHandler::new(expected_location)) as _ })); use eyre::ContextCompat;