diff --git a/Cargo.toml b/Cargo.toml index ee99ab7..4470692 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ rustversion = "1.0" thiserror = "1.0" trybuild = { version = "1.0.19", features = ["diff"] } +[dependencies] +once_cell = "1.4.0" + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] rustdoc-args = ["--cfg", "doc_cfg"] diff --git a/src/backtrace.rs b/src/backtrace.rs index 01e33cb..ad2eaaf 100644 --- a/src/backtrace.rs +++ b/src/backtrace.rs @@ -1,23 +1,3 @@ -#[cfg(backtrace)] -pub(crate) use std::backtrace::Backtrace; - -#[cfg(not(backtrace))] -pub(crate) enum Backtrace {} - -#[cfg(backtrace)] -macro_rules! backtrace { - () => { - Some(Backtrace::capture()) - }; -} - -#[cfg(not(backtrace))] -macro_rules! backtrace { - () => { - None - }; -} - #[cfg(backtrace)] macro_rules! backtrace_if_absent { ($err:expr) => { @@ -27,10 +7,3 @@ macro_rules! backtrace_if_absent { } }; } - -#[cfg(all(feature = "std", not(backtrace)))] -macro_rules! backtrace_if_absent { - ($err:expr) => { - None - }; -} diff --git a/src/context.rs b/src/context.rs index 25d3411..cf85f8a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -24,8 +24,7 @@ mod ext { where C: Display + Send + Sync + 'static, { - let backtrace = backtrace_if_absent!(self); - Error::from_context(context, self, backtrace) + Error::from_context(context, self) } } @@ -84,7 +83,7 @@ impl Context for Option { where C: Display + Send + Sync + 'static, { - self.ok_or_else(|| Error::from_display(context, backtrace!())) + self.ok_or_else(|| Error::from_display(context)) } fn with_context(self, context: F) -> Result @@ -92,7 +91,7 @@ impl Context for Option { C: Display + Send + Sync + 'static, F: FnOnce() -> C, { - self.ok_or_else(|| Error::from_display(context(), backtrace!())) + self.ok_or_else(|| Error::from_display(context())) } } diff --git a/src/error.rs b/src/error.rs index 80d879f..dac6d34 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,12 @@ use crate::alloc::Box; -use crate::backtrace::Backtrace; use crate::chain::Chain; use crate::{Error, StdError}; use core::any::TypeId; use core::fmt::{self, Debug, Display}; use core::mem::{self, ManuallyDrop}; use core::ptr::{self, NonNull}; +#[cfg(backtrace)] +use std::backtrace::Backtrace; #[cfg(feature = "std")] use core::ops::{Deref, DerefMut}; @@ -24,8 +25,7 @@ impl Error { where E: StdError + Send + Sync + 'static, { - let backtrace = backtrace_if_absent!(error); - Error::from_std(error, backtrace) + Error::from_std(error) } /// Create a new error object from a printable error message. @@ -69,11 +69,11 @@ impl Error { where M: Display + Debug + Send + Sync + 'static, { - Error::from_adhoc(message, backtrace!()) + Error::from_adhoc(message) } #[cfg(feature = "std")] - pub(crate) fn from_std(error: E, backtrace: Option) -> Self + pub(crate) fn from_std(error: E) -> Self where E: StdError + Send + Sync + 'static, { @@ -88,10 +88,10 @@ impl Error { }; // Safety: passing vtable that operates on the right type E. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { Error::construct(error, vtable) } } - pub(crate) fn from_adhoc(message: M, backtrace: Option) -> Self + pub(crate) fn from_adhoc(message: M) -> Self where M: Display + Debug + Send + Sync + 'static, { @@ -109,10 +109,10 @@ impl Error { // Safety: MessageError is repr(transparent) so it is okay for the // vtable to allow casting the MessageError to M. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { Error::construct(error, vtable) } } - pub(crate) fn from_display(message: M, backtrace: Option) -> Self + pub(crate) fn from_display(message: M) -> Self where M: Display + Send + Sync + 'static, { @@ -130,11 +130,11 @@ impl Error { // Safety: DisplayError is repr(transparent) so it is okay for the // vtable to allow casting the DisplayError to M. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { Error::construct(error, vtable) } } #[cfg(feature = "std")] - pub(crate) fn from_context(context: C, error: E, backtrace: Option) -> Self + pub(crate) fn from_context(context: C, error: E) -> Self where C: Display + Send + Sync + 'static, E: StdError + Send + Sync + 'static, @@ -152,14 +152,11 @@ impl Error { }; // Safety: passing vtable that operates on the right type. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { Error::construct(error, vtable) } } #[cfg(feature = "std")] - pub(crate) fn from_boxed( - error: Box, - backtrace: Option, - ) -> Self { + pub(crate) fn from_boxed(error: Box) -> Self { use crate::wrapper::BoxedError; let error = BoxedError(error); let vtable = &ErrorVTable { @@ -174,7 +171,7 @@ impl Error { // Safety: BoxedError is repr(transparent) so it is okay for the vtable // to allow casting to Box. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { Error::construct(error, vtable) } } // Takes backtrace as argument rather than capturing it here so that the @@ -182,17 +179,18 @@ impl Error { // // Unsafe because the given vtable must have sensible behavior on the error // value of type E. - unsafe fn construct( - error: E, - vtable: &'static ErrorVTable, - backtrace: Option, - ) -> Self + unsafe fn construct(error: E, vtable: &'static ErrorVTable) -> Self where E: StdError + Send + Sync + 'static, { + let handler = crate::HOOK + .get_or_init(|| Box::new(|error| Box::new(crate::DefaultHandler::new(error))))( + &error + ); + let inner = Box::new(ErrorImpl { vtable, - backtrace, + handler, _object: error, }); // Erase the concrete type of E from the compile-time type system. This @@ -279,11 +277,8 @@ impl Error { object_drop_rest: context_chain_drop_rest::, }; - // As the cause is anyhow::Error, we already have a backtrace for it. - let backtrace = None; - // Safety: passing vtable that operates on the right type. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { Error::construct(error, vtable) } } /// Get the backtrace for this Error. @@ -310,6 +305,16 @@ impl Error { self.inner.backtrace() } + #[cfg(feature = "std")] + pub fn handler(&self) -> &dyn crate::ReportHandler { + self.inner.handler() + } + + #[cfg(feature = "std")] + pub fn handler_mut(&mut self) -> &mut dyn crate::ReportHandler { + self.inner.handler_mut() + } + /// An iterator of the chain of source errors contained by this Error. /// /// This iterator will visit every error in the cause chain of this error @@ -469,8 +474,7 @@ where E: StdError + Send + Sync + 'static, { fn from(error: E) -> Self { - let backtrace = backtrace_if_absent!(error); - Error::from_std(error, backtrace) + Error::from_std(error) } } @@ -680,7 +684,7 @@ where #[repr(C)] pub(crate) struct ErrorImpl { vtable: &'static ErrorVTable, - backtrace: Option, + handler: Box, // NOTE: Don't use directly. Use only through vtable. Erased type may have // different alignment. _object: E, @@ -722,12 +726,19 @@ impl ErrorImpl<()> { // This unwrap can only panic if the underlying error's backtrace method // is nondeterministic, which would only happen in maliciously // constructed code. - self.backtrace - .as_ref() - .or_else(|| self.error().backtrace()) - .expect("backtrace capture failed") + self.handler.backtrace(self.error()) + } + + pub(crate) fn handler(&self) -> &dyn crate::ReportHandler { + self.handler.as_ref() } + #[cfg(feature = "std")] + pub(crate) fn handler_mut(&mut self) -> &mut dyn crate::ReportHandler { + self.handler.as_mut() + } + + #[cfg(feature = "std")] pub(crate) fn chain(&self) -> Chain { Chain::new(self.error()) } diff --git a/src/fmt.rs b/src/fmt.rs index be93e1a..e2f6997 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,25 +1,42 @@ use crate::chain::Chain; use crate::error::ErrorImpl; -use core::fmt::{self, Debug, Write}; +use core::fmt::{self, Write}; impl ErrorImpl<()> { pub(crate) fn display(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.error())?; - - if f.alternate() { - for cause in self.chain().skip(1) { - write!(f, ": {}", cause)?; - } - } + let error = self.error(); + let handler = self.handler(); - Ok(()) + handler.display(error, f) } pub(crate) fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result { let error = self.error(); + let handler = self.handler(); + + handler.debug(error, f) + } +} + +impl crate::ReportHandler for crate::DefaultHandler { + #[cfg(backtrace)] + fn backtrace<'a>( + &'a self, + error: &'a (dyn crate::StdError + 'static), + ) -> &'a std::backtrace::Backtrace { + error + .backtrace() + .or_else(|| self.backtrace.as_ref()) + .expect("backtrace must have been captured") + } + fn debug( + &self, + error: &(dyn crate::StdError + 'static), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { if f.alternate() { - return Debug::fmt(error, f); + return core::fmt::Debug::fmt(error, f); } write!(f, "{}", error)?; @@ -42,7 +59,7 @@ impl ErrorImpl<()> { { use std::backtrace::BacktraceStatus; - let backtrace = self.backtrace(); + let backtrace = self.backtrace(error); if let BacktraceStatus::Captured = backtrace.status() { let mut backtrace = backtrace.to_string(); write!(f, "\n\n")?; @@ -97,7 +114,7 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use super::*; diff --git a/src/kind.rs b/src/kind.rs index fdeb060..9574b06 100644 --- a/src/kind.rs +++ b/src/kind.rs @@ -50,9 +50,6 @@ use core::fmt::{Debug, Display}; #[cfg(feature = "std")] use crate::StdError; -#[cfg(backtrace)] -use std::backtrace::Backtrace; - pub struct Adhoc; pub trait AdhocKind: Sized { @@ -69,7 +66,7 @@ impl Adhoc { where M: Display + Debug + Send + Sync + 'static, { - Error::from_adhoc(message, backtrace!()) + Error::from_adhoc(message) } } @@ -110,7 +107,6 @@ impl BoxedKind for Box {} #[cfg(feature = "std")] impl Boxed { pub fn new(self, error: Box) -> Error { - let backtrace = backtrace_if_absent!(error); - Error::from_boxed(error, backtrace) + Error::from_boxed(error) } } diff --git a/src/lib.rs b/src/lib.rs index 684c1ab..dbf4a5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -239,6 +239,9 @@ use core::fmt::Debug; #[cfg(feature = "std")] use std::error::Error as StdError; +#[cfg(backtrace)] +use std::backtrace::Backtrace; + #[cfg(not(feature = "std"))] trait StdError: Debug + Display { fn source(&self) -> Option<&(dyn StdError + 'static)> { @@ -247,6 +250,7 @@ trait StdError: Debug + Display { } pub use anyhow as format_err; +use once_cell::sync::OnceCell; /// The `Error` type, a wrapper around a dynamic error type. /// @@ -584,15 +588,147 @@ pub trait Context: context::private::Sealed { F: FnOnce() -> C; } +static HOOK: OnceCell = OnceCell::new(); + +#[cfg(feature = "std")] +pub trait ReportHandler: core::any::Any + Send + Sync { + fn debug( + &self, + error: &(dyn StdError + 'static), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result; + + #[cfg(backtrace)] + fn backtrace<'a>(&'a self, error: &'a (dyn StdError + 'static)) -> &'a Backtrace; + + /// Override for the `Display` format + fn display( + &self, + error: &(dyn StdError + 'static), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + write!(f, "{}", error)?; + + if f.alternate() { + for cause in crate::chain::Chain::new(error).skip(1) { + write!(f, ": {}", cause)?; + } + } + + Ok(()) + } +} + +#[cfg(not(feature = "std"))] +trait ReportHandler: core::any::Any + Send + Sync { + fn debug( + &self, + error: &(dyn StdError + 'static), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result; + + #[cfg(backtrace)] + fn backtrace<'a>(&'a self, error: &'a (dyn StdError + 'static)) -> &'a Backtrace; + + /// Override for the `Display` format + fn display( + &self, + error: &(dyn StdError + 'static), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + write!(f, "{}", error)?; + + if f.alternate() { + for cause in crate::chain::Chain::new(error).skip(1) { + write!(f, ": {}", cause)?; + } + } + + Ok(()) + } +} + +type ErrorHook = Box< + dyn Fn(&(dyn StdError + 'static)) -> Box + + Sync + + Send + + 'static, +>; + +#[derive(Debug)] +#[cfg(feature = "std")] +pub struct HookError; + +#[cfg(feature = "std")] +impl core::fmt::Display for HookError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("unable to set global hook") + } +} + +#[cfg(feature = "std")] +impl StdError for HookError {} + +#[cfg(feature = "std")] +pub fn set_hook(hook: ErrorHook) -> Result<(), HookError> { + HOOK.set(hook).map_err(|_| HookError) +} + +#[cfg(feature = "std")] +impl dyn ReportHandler { + pub fn is(&self) -> bool { + // Get `TypeId` of the type this function is instantiated with. + let t = core::any::TypeId::of::(); + + // Get `TypeId` of the type in the trait object (`self`). + let concrete = self.type_id(); + + // Compare both `TypeId`s on equality. + t == concrete + } + + pub fn downcast_ref(&self) -> Option<&T> { + if self.is::() { + unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) } + } else { + None + } + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> { + if self.is::() { + unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) } + } else { + None + } + } +} + +struct DefaultHandler { + #[cfg(backtrace)] + backtrace: Option, +} + +impl DefaultHandler { + #[cfg(backtrace)] + fn new(error: &(dyn StdError + 'static)) -> Self { + let backtrace = backtrace_if_absent!(error); + + Self { backtrace } + } + + #[cfg(not(backtrace))] + fn new(_error: &(dyn StdError + 'static)) -> Self { + Self {} + } +} + // Not public API. Referenced by macro-generated code. #[doc(hidden)] pub mod private { use crate::Error; use core::fmt::{Debug, Display}; - #[cfg(backtrace)] - use std::backtrace::Backtrace; - pub use core::result::Result::Err; #[doc(hidden)] @@ -607,6 +743,6 @@ pub mod private { where M: Display + Debug + Send + Sync + 'static, { - Error::from_adhoc(message, backtrace!()) + Error::from_adhoc(message) } } diff --git a/src/wrapper.rs b/src/wrapper.rs index 3ebe51a..99c3482 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -68,7 +68,7 @@ impl Display for BoxedError { #[cfg(feature = "std")] impl StdError for BoxedError { #[cfg(backtrace)] - fn backtrace(&self) -> Option<&crate::backtrace::Backtrace> { + fn backtrace(&self) -> Option<&std::backtrace::Backtrace> { self.0.backtrace() } diff --git a/tests/test_handler.rs b/tests/test_handler.rs new file mode 100644 index 0000000..b59155a --- /dev/null +++ b/tests/test_handler.rs @@ -0,0 +1,60 @@ +#![cfg_attr(backtrace, feature(backtrace))] + +struct CustomHandler { + msg: &'static str, +} + +impl anyhow::ReportHandler for CustomHandler { + #[cfg(backtrace)] + fn backtrace<'a>( + &'a self, + _error: &'a (dyn std::error::Error + 'static), + ) -> &std::backtrace::Backtrace { + unimplemented!() + } + + fn debug( + &self, + _error: &(dyn std::error::Error + 'static), + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(f, "{}", self.msg) + } +} + +static EXPECTED: &str = "hook is set!"; + +#[test] +fn test_custom_hook() { + // discard the result because the tests in the same file race against + // eachother to set the global hook and one will panic + let _ = anyhow::set_hook(Box::new(move |_error| { + Box::new(CustomHandler { msg: EXPECTED }) + })); + + let report = anyhow::anyhow!("heres the message!"); + let actual = format!("{:?}", report); + + assert_eq!(EXPECTED, actual); +} + +#[test] +fn test_mutable_hook() { + let real_expected = "the context was modified!"; + + // discard the result because the tests in the same file race against + // eachother to set the global hook and one will panic + let _ = anyhow::set_hook(Box::new(move |_error| { + Box::new(CustomHandler { msg: EXPECTED }) + })); + + let mut report = anyhow::anyhow!("heres the message!"); + report + .handler_mut() + .downcast_mut::() + .unwrap() + .msg = real_expected; + let actual = format!("{:?}", report); + + assert_eq!(real_expected, actual); +} diff --git a/tests/test_macros.rs b/tests/test_macros.rs index c6888b6..b9339b0 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -11,6 +11,7 @@ fn test_messages() { } #[test] +#[allow(clippy::eq_op)] fn test_ensure() { let f = || { ensure!(1 + 1 == 2, "This is correct");