From 29aec5809920b43a810e70256ada76a289faa294 Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Wed, 29 Nov 2023 12:38:54 +0100 Subject: [PATCH] feat: Report builder Adds a more flexible method to construct reports, which will allow things such as providing an explicit backtrace, using a local eyre hook, etc. --- eyre/src/builder.rs | 79 +++++++++++++ eyre/src/context.rs | 3 +- eyre/src/error.rs | 263 ++------------------------------------------ eyre/src/fmt.rs | 2 +- eyre/src/lib.rs | 4 +- eyre/src/vtable.rs | 230 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 327 insertions(+), 254 deletions(-) create mode 100644 eyre/src/builder.rs create mode 100644 eyre/src/vtable.rs diff --git a/eyre/src/builder.rs b/eyre/src/builder.rs new file mode 100644 index 0000000..0e6ab26 --- /dev/null +++ b/eyre/src/builder.rs @@ -0,0 +1,79 @@ +use crate::{ + backtrace::Backtrace, + vtable::{ + object_boxed, object_downcast, object_downcast_mut, object_drop, object_drop_front, + object_mut, object_ref, ErrorVTable, + }, + Report, StdError, +}; +use std::fmt::Display; + +#[derive(Default)] +pub struct ReportBuilder { + backtrace: Option, +} + +impl std::fmt::Debug for ReportBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl ReportBuilder { + 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; + self + } + + #[cfg_attr(track_caller, track_caller)] + /// Creates a report from the given error message + pub fn msg(self, message: M) -> Report + where + M: Display + std::fmt::Debug + Send + Sync + 'static, + { + use crate::wrapper::MessageError; + let error: MessageError = MessageError(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: 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)); + + 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 + where + E: StdError + Send + Sync + 'static, + { + 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: passing vtable that operates on the right type E. + let handler = Some(crate::capture_handler(&error)); + + unsafe { Report::construct(error, vtable, handler) } + } +} diff --git a/eyre/src/context.rs b/eyre/src/context.rs index a15b41b..152e7c9 100644 --- a/eyre/src/context.rs +++ b/eyre/src/context.rs @@ -1,4 +1,5 @@ -use crate::error::{ContextError, ErrorImpl}; +use crate::error::ContextError; +use crate::vtable::ErrorImpl; use crate::{ContextCompat, Report, StdError, WrapErr}; use core::fmt::{self, Debug, Display, Write}; diff --git a/eyre/src/error.rs b/eyre/src/error.rs index 701a0fd..3632778 100644 --- a/eyre/src/error.rs +++ b/eyre/src/error.rs @@ -1,10 +1,15 @@ +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, +}; use crate::EyreHandler; use crate::{Report, StdError}; use core::any::TypeId; use core::fmt::{self, Debug, Display}; -use core::mem::{self, ManuallyDrop}; +use core::mem::ManuallyDrop; use core::ptr::{self, NonNull}; use core::ops::{Deref, DerefMut}; @@ -22,7 +27,7 @@ impl Report { where E: StdError + Send + Sync + 'static, { - Report::from_std(error) + ReportBuilder::default().report(error) } /// Create a new error object from a printable error message. @@ -67,29 +72,7 @@ impl Report { where M: Display + Debug + Send + Sync + 'static, { - Report::from_adhoc(message) - } - - #[cfg_attr(track_caller, track_caller)] - /// Creates a new error from an implementor of [`std::error::Error`] - pub(crate) fn from_std(error: E) -> Self - where - E: StdError + Send + Sync + 'static, - { - 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: passing vtable that operates on the right type E. - let handler = Some(crate::capture_handler(&error)); - - unsafe { Report::construct(error, vtable, handler) } + ReportBuilder::default().msg(message) } #[cfg_attr(track_caller, track_caller)] @@ -97,23 +80,7 @@ impl Report { where M: Display + Debug + Send + Sync + 'static, { - use crate::wrapper::MessageError; - let error: MessageError = MessageError(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: 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)); - - unsafe { Report::construct(error, vtable, handler) } + ReportBuilder::default().msg(message) } #[cfg_attr(track_caller, track_caller)] @@ -141,6 +108,7 @@ impl Report { } #[cfg_attr(track_caller, track_caller)] + /// Wraps a source error with a message pub(crate) fn from_msg(msg: D, error: E) -> Self where D: Display + Send + Sync + 'static, @@ -190,7 +158,7 @@ impl Report { // // Unsafe because the given vtable must have sensible behavior on the error // value of type E. - unsafe fn construct( + pub(crate) unsafe fn construct( error: E, vtable: &'static ErrorVTable, handler: Option>, @@ -493,7 +461,7 @@ where { #[cfg_attr(track_caller, track_caller)] fn from(error: E) -> Self { - Report::from_std(error) + ReportBuilder::default().report(error) } } @@ -532,113 +500,6 @@ impl Drop for Report { } } -struct ErrorVTable { - object_drop: unsafe fn(OwnedPtr>), - object_ref: unsafe fn(RefPtr<'_, ErrorImpl<()>>) -> &(dyn StdError + Send + Sync + 'static), - object_mut: unsafe fn(MutPtr<'_, ErrorImpl<()>>) -> &mut (dyn StdError + Send + Sync + 'static), - #[allow(clippy::type_complexity)] - object_boxed: unsafe fn(OwnedPtr>) -> Box, - object_downcast: unsafe fn(RefPtr<'_, ErrorImpl<()>>, TypeId) -> Option>, - object_downcast_mut: unsafe fn(MutPtr<'_, ErrorImpl<()>>, TypeId) -> Option>, - object_drop_rest: unsafe fn(OwnedPtr>, TypeId), -} - -/// # Safety -/// -/// Requires layout of *e to match ErrorImpl. -unsafe fn object_drop(e: OwnedPtr>) { - // Cast to a context type and drop the Box allocation. - let unerased = unsafe { e.cast::>().into_box() }; - drop(unerased); -} - -/// # Safety -/// -/// Requires layout of *e to match ErrorImpl. -unsafe fn object_drop_front(e: OwnedPtr>, target: TypeId) { - // Drop the fields of ErrorImpl other than E as well as the Box allocation, - // without dropping E itself. This is used by downcast after doing a - // ptr::read to take ownership of the E. - let _ = target; - // Note: This must not use `mem::transmute` because it tries to reborrow the `Unique` - // contained in `Box`, which must not be done. In practice this probably won't make any - // difference by now, but technically it's unsound. - // see: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.m - let unerased = unsafe { e.cast::>().into_box() }; - - mem::forget(unerased._object) -} - -/// # Safety -/// -/// Requires layout of *e to match ErrorImpl. -unsafe fn object_ref(e: RefPtr<'_, ErrorImpl<()>>) -> &(dyn StdError + Send + Sync + 'static) -where - E: StdError + Send + Sync + 'static, -{ - // Attach E's native StdError vtable onto a pointer to self._object. - &unsafe { e.cast::>().as_ref() }._object -} - -/// # Safety -/// -/// Requires layout of *e to match ErrorImpl. -unsafe fn object_mut(e: MutPtr<'_, ErrorImpl<()>>) -> &mut (dyn StdError + Send + Sync + 'static) -where - E: StdError + Send + Sync + 'static, -{ - // Attach E's native StdError vtable onto a pointer to self._object. - &mut unsafe { e.cast::>().into_mut() }._object -} - -/// # Safety -/// -/// Requires layout of *e to match ErrorImpl. -unsafe fn object_boxed(e: OwnedPtr>) -> Box -where - E: StdError + Send + Sync + 'static, -{ - // Attach ErrorImpl's native StdError vtable. The StdError impl is below. - unsafe { e.cast::>().into_box() } -} - -/// # Safety -/// -/// Requires layout of *e to match ErrorImpl. -unsafe fn object_downcast(e: RefPtr<'_, ErrorImpl<()>>, target: TypeId) -> Option> -where - E: 'static, -{ - if TypeId::of::() == target { - // Caller is looking for an E pointer and e is ErrorImpl, take a - // pointer to its E field. - let unerased = unsafe { e.cast::>().as_ref() }; - Some(NonNull::from(&(unerased._object)).cast::<()>()) - } else { - None - } -} - -/// # Safety -/// -/// Requires layout of *e to match ErrorImpl. -unsafe fn object_downcast_mut( - e: MutPtr<'_, ErrorImpl<()>>, - target: TypeId, -) -> Option> -where - E: 'static, -{ - if TypeId::of::() == target { - // Caller is looking for an E pointer and e is ErrorImpl, take a - // pointer to its E field. - let unerased = unsafe { e.cast::>().into_mut() }; - Some(NonNull::from(&mut (unerased._object)).cast::<()>()) - } else { - None - } -} - /// # Safety /// /// Requires layout of *e to match ErrorImpl>. @@ -782,21 +643,6 @@ where } } -#[repr(C)] -pub(crate) struct ErrorHeader { - vtable: &'static ErrorVTable, - pub(crate) handler: Option>, -} - -// repr C to ensure that E remains in the final position. -#[repr(C)] -pub(crate) struct ErrorImpl { - header: ErrorHeader, - // NOTE: Don't use directly. Use only through vtable. Erased type may have - // different alignment. - _object: E, -} - // repr C to ensure that ContextError has the same layout as // ContextError, E> and ContextError>. #[repr(C)] @@ -805,91 +651,6 @@ pub(crate) struct ContextError { pub(crate) error: E, } -impl ErrorImpl { - /// Returns a type erased Error - fn erase(&self) -> RefPtr<'_, ErrorImpl<()>> { - // Erase the concrete type of E but preserve the vtable in self.vtable - // for manipulating the resulting thin pointer. This is analogous to an - // unsize coersion. - RefPtr::new(self).cast() - } -} - -// Reads the header out of `p`. This is the same as `p.as_ref().header`, but -// avoids converting `p` into a reference of a shrunk provenance with a type different than the -// allocation. -fn header(p: RefPtr<'_, ErrorImpl<()>>) -> &'_ ErrorHeader { - // Safety: `ErrorHeader` is the first field of repr(C) `ErrorImpl` - unsafe { p.cast().as_ref() } -} - -fn header_mut(p: MutPtr<'_, ErrorImpl<()>>) -> &mut ErrorHeader { - // Safety: `ErrorHeader` is the first field of repr(C) `ErrorImpl` - unsafe { p.cast().into_mut() } -} - -impl ErrorImpl<()> { - pub(crate) fn error(this: RefPtr<'_, Self>) -> &(dyn StdError + Send + Sync + 'static) { - // Use vtable to attach E's native StdError vtable for the right - // original type E. - unsafe { (header(this).vtable.object_ref)(this) } - } - - pub(crate) fn error_mut(this: MutPtr<'_, Self>) -> &mut (dyn StdError + Send + Sync + 'static) { - // Use vtable to attach E's native StdError vtable for the right - // original type E. - unsafe { (header_mut(this).vtable.object_mut)(this) } - } - - pub(crate) fn chain(this: RefPtr<'_, Self>) -> Chain<'_> { - Chain::new(Self::error(this)) - } - - pub(crate) fn header(this: RefPtr<'_, ErrorImpl>) -> &ErrorHeader { - header(this) - } -} - -impl StdError for ErrorImpl -where - E: StdError, -{ - fn source(&self) -> Option<&(dyn StdError + 'static)> { - ErrorImpl::<()>::error(self.erase()).source() - } -} - -impl Debug for ErrorImpl -where - E: Debug, -{ - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - ErrorImpl::debug(self.erase(), formatter) - } -} - -impl Display for ErrorImpl -where - E: Display, -{ - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - Display::fmt(ErrorImpl::error(self.erase()), formatter) - } -} - -impl From for Box { - fn from(error: Report) -> Self { - let outer = ManuallyDrop::new(error); - unsafe { - // Read Box> from error. Can't move it out because - // Report has a Drop impl which we want to not run. - // Use vtable to attach ErrorImpl's native StdError vtable for - // the right original type E. - (header(outer.inner.as_ref()).vtable.object_boxed)(outer.inner) - } - } -} - impl From for Box { fn from(error: Report) -> Self { Box::::from(error) diff --git a/eyre/src/fmt.rs b/eyre/src/fmt.rs index c66620e..bff6d11 100644 --- a/eyre/src/fmt.rs +++ b/eyre/src/fmt.rs @@ -1,4 +1,4 @@ -use crate::{error::ErrorImpl, ptr::RefPtr}; +use crate::{ptr::RefPtr, vtable::ErrorImpl}; use core::fmt; impl ErrorImpl<()> { diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index bfafb09..aeb14cb 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -362,12 +362,13 @@ mod macros; mod ptr; mod wrapper; +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 crate::error::ErrorImpl; use core::fmt::Display; use std::error::Error as StdError; @@ -377,6 +378,7 @@ pub use eyre as format_err; pub use eyre as anyhow; use once_cell::sync::OnceCell; use ptr::OwnedPtr; +use vtable::ErrorImpl; #[doc(hidden)] pub use DefaultHandler as DefaultContext; #[doc(hidden)] diff --git a/eyre/src/vtable.rs b/eyre/src/vtable.rs new file mode 100644 index 0000000..b4ed14c --- /dev/null +++ b/eyre/src/vtable.rs @@ -0,0 +1,230 @@ +use std::{ + any::TypeId, + fmt::{self, Debug, Display}, + mem::{self, ManuallyDrop}, + ptr::NonNull, +}; + +use crate::{ + ptr::{MutPtr, OwnedPtr, RefPtr}, + Chain, EyreContext, EyreHandler, Report, StdError, +}; + +pub(crate) struct ErrorVTable { + pub(crate) object_drop: unsafe fn(OwnedPtr>), + pub(crate) object_ref: + unsafe fn(RefPtr<'_, ErrorImpl<()>>) -> &(dyn StdError + Send + Sync + 'static), + pub(crate) object_mut: + unsafe fn(MutPtr<'_, ErrorImpl<()>>) -> &mut (dyn StdError + Send + Sync + 'static), + #[allow(clippy::type_complexity)] + pub(crate) object_boxed: + unsafe fn(OwnedPtr>) -> Box, + pub(crate) object_downcast: unsafe fn(RefPtr<'_, ErrorImpl<()>>, TypeId) -> Option>, + pub(crate) object_downcast_mut: + unsafe fn(MutPtr<'_, ErrorImpl<()>>, TypeId) -> Option>, + pub(crate) object_drop_rest: unsafe fn(OwnedPtr>, TypeId), +} + +// repr C to ensure that E remains in the final position. +#[repr(C)] +pub(crate) struct ErrorImpl { + pub(crate) header: ErrorHeader, + // NOTE: Don't use directly. Use only through vtable. Erased type may have + // different alignment. + pub(crate) _object: E, +} + +#[repr(C)] +pub(crate) struct ErrorHeader { + pub(crate) vtable: &'static ErrorVTable, + pub(crate) handler: Option>, +} +// Reads the header out of `p`. This is the same as `p.as_ref().header`, but +// avoids converting `p` into a reference of a shrunk provenance with a type different than the +// allocation. +pub(crate) fn header(p: RefPtr<'_, ErrorImpl<()>>) -> &'_ ErrorHeader { + // Safety: `ErrorHeader` is the first field of repr(C) `ErrorImpl` + unsafe { p.cast().as_ref() } +} + +pub(crate) fn header_mut(p: MutPtr<'_, ErrorImpl<()>>) -> &mut ErrorHeader { + // Safety: `ErrorHeader` is the first field of repr(C) `ErrorImpl` + unsafe { p.cast().into_mut() } +} + +impl ErrorImpl { + /// Returns a type erased Error + fn erase(&self) -> RefPtr<'_, ErrorImpl<()>> { + // Erase the concrete type of E but preserve the vtable in self.vtable + // for manipulating the resulting thin pointer. This is analogous to an + // unsize coersion. + RefPtr::new(self).cast() + } +} + +impl ErrorImpl<()> { + pub(crate) fn error(this: RefPtr<'_, Self>) -> &(dyn StdError + Send + Sync + 'static) { + // Use vtable to attach E's native StdError vtable for the right + // original type E. + unsafe { (header(this).vtable.object_ref)(this) } + } + + pub(crate) fn error_mut(this: MutPtr<'_, Self>) -> &mut (dyn StdError + Send + Sync + 'static) { + // Use vtable to attach E's native StdError vtable for the right + // original type E. + unsafe { (header_mut(this).vtable.object_mut)(this) } + } + + pub(crate) fn chain(this: RefPtr<'_, Self>) -> Chain<'_> { + Chain::new(Self::error(this)) + } + + pub(crate) fn header(this: RefPtr<'_, ErrorImpl>) -> &ErrorHeader { + header(this) + } +} + +impl StdError for ErrorImpl +where + E: StdError, +{ + fn source(&self) -> Option<&(dyn StdError + 'static)> { + ErrorImpl::<()>::error(self.erase()).source() + } +} + +impl Debug for ErrorImpl +where + E: Debug, +{ + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + ErrorImpl::debug(self.erase(), formatter) + } +} + +impl Display for ErrorImpl +where + E: Display, +{ + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(ErrorImpl::error(self.erase()), formatter) + } +} + +impl From for Box { + fn from(error: Report) -> Self { + let outer = ManuallyDrop::new(error); + unsafe { + // Read Box> from error. Can't move it out because + // Report has a Drop impl which we want to not run. + // Use vtable to attach ErrorImpl's native StdError vtable for + // the right original type E. + (header(outer.inner.as_ref()).vtable.object_boxed)(outer.inner) + } + } +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +pub(crate) unsafe fn object_drop(e: OwnedPtr>) { + // Cast to a context type and drop the Box allocation. + let unerased = unsafe { e.cast::>().into_box() }; + drop(unerased); +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +pub(crate) unsafe fn object_drop_front(e: OwnedPtr>, target: TypeId) { + // Drop the fields of ErrorImpl other than E as well as the Box allocation, + // without dropping E itself. This is used by downcast after doing a + // ptr::read to take ownership of the E. + let _ = target; + // Note: This must not use `mem::transmute` because it tries to reborrow the `Unique` + // contained in `Box`, which must not be done. In practice this probably won't make any + // difference by now, but technically it's unsound. + // see: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.m + let unerased = unsafe { e.cast::>().into_box() }; + + mem::forget(unerased._object) +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +pub(crate) unsafe fn object_ref( + e: RefPtr<'_, ErrorImpl<()>>, +) -> &(dyn StdError + Send + Sync + 'static) +where + E: StdError + Send + Sync + 'static, +{ + // Attach E's native StdError vtable onto a pointer to self._object. + &unsafe { e.cast::>().as_ref() }._object +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +pub(crate) unsafe fn object_mut( + e: MutPtr<'_, ErrorImpl<()>>, +) -> &mut (dyn StdError + Send + Sync + 'static) +where + E: StdError + Send + Sync + 'static, +{ + // Attach E's native StdError vtable onto a pointer to self._object. + &mut unsafe { e.cast::>().into_mut() }._object +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +pub(crate) unsafe fn object_boxed( + e: OwnedPtr>, +) -> Box +where + E: StdError + Send + Sync + 'static, +{ + // Attach ErrorImpl's native StdError vtable. The StdError impl is below. + unsafe { e.cast::>().into_box() } +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +pub(crate) unsafe fn object_downcast( + e: RefPtr<'_, ErrorImpl<()>>, + target: TypeId, +) -> Option> +where + E: 'static, +{ + if TypeId::of::() == target { + // Caller is looking for an E pointer and e is ErrorImpl, take a + // pointer to its E field. + let unerased = unsafe { e.cast::>().as_ref() }; + Some(NonNull::from(&(unerased._object)).cast::<()>()) + } else { + None + } +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +pub(crate) unsafe fn object_downcast_mut( + e: MutPtr<'_, ErrorImpl<()>>, + target: TypeId, +) -> Option> +where + E: 'static, +{ + if TypeId::of::() == target { + // Caller is looking for an E pointer and e is ErrorImpl, take a + // pointer to its E field. + let unerased = unsafe { e.cast::>().into_mut() }; + Some(NonNull::from(&mut (unerased._object)).cast::<()>()) + } else { + None + } +}