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.
This commit is contained in:
Freja Roberts 2023-11-29 12:38:54 +01:00
parent 76175f84fc
commit 29aec58099
6 changed files with 327 additions and 254 deletions

79
eyre/src/builder.rs Normal file
View File

@ -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<Backtrace>,
}
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<Backtrace>) -> Self {
self.backtrace = backtrace;
self
}
#[cfg_attr(track_caller, track_caller)]
/// Creates a report from the given error message
pub fn msg<M>(self, message: M) -> Report
where
M: Display + std::fmt::Debug + Send + Sync + 'static,
{
use crate::wrapper::MessageError;
let error: MessageError<M> = MessageError(message);
let vtable = &ErrorVTable {
object_drop: object_drop::<MessageError<M>>,
object_ref: object_ref::<MessageError<M>>,
object_mut: object_mut::<MessageError<M>>,
object_boxed: object_boxed::<MessageError<M>>,
object_downcast: object_downcast::<M>,
object_downcast_mut: object_downcast_mut::<M>,
object_drop_rest: object_drop_front::<M>,
};
// Safety: MessageError is repr(transparent) so it is okay for the
// vtable to allow casting the MessageError<M> 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<E>(self, error: E) -> Report
where
E: StdError + Send + Sync + 'static,
{
let vtable = &ErrorVTable {
object_drop: object_drop::<E>,
object_ref: object_ref::<E>,
object_mut: object_mut::<E>,
object_boxed: object_boxed::<E>,
object_downcast: object_downcast::<E>,
object_downcast_mut: object_downcast_mut::<E>,
object_drop_rest: object_drop_front::<E>,
};
// Safety: passing vtable that operates on the right type E.
let handler = Some(crate::capture_handler(&error));
unsafe { Report::construct(error, vtable, handler) }
}
}

View File

@ -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};

View File

@ -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<E>(error: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
let vtable = &ErrorVTable {
object_drop: object_drop::<E>,
object_ref: object_ref::<E>,
object_mut: object_mut::<E>,
object_boxed: object_boxed::<E>,
object_downcast: object_downcast::<E>,
object_downcast_mut: object_downcast_mut::<E>,
object_drop_rest: object_drop_front::<E>,
};
// 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<M> = MessageError(message);
let vtable = &ErrorVTable {
object_drop: object_drop::<MessageError<M>>,
object_ref: object_ref::<MessageError<M>>,
object_mut: object_mut::<MessageError<M>>,
object_boxed: object_boxed::<MessageError<M>>,
object_downcast: object_downcast::<M>,
object_downcast_mut: object_downcast_mut::<M>,
object_drop_rest: object_drop_front::<M>,
};
// Safety: MessageError is repr(transparent) so it is okay for the
// vtable to allow casting the MessageError<M> 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<D, E>(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<E>(
pub(crate) unsafe fn construct<E>(
error: E,
vtable: &'static ErrorVTable,
handler: Option<Box<dyn EyreHandler>>,
@ -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<ErrorImpl<()>>),
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<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>,
object_downcast: unsafe fn(RefPtr<'_, ErrorImpl<()>>, TypeId) -> Option<NonNull<()>>,
object_downcast_mut: unsafe fn(MutPtr<'_, ErrorImpl<()>>, TypeId) -> Option<NonNull<()>>,
object_drop_rest: unsafe fn(OwnedPtr<ErrorImpl<()>>, TypeId),
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
unsafe fn object_drop<E>(e: OwnedPtr<ErrorImpl<()>>) {
// Cast to a context type and drop the Box allocation.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().into_box() };
drop(unerased);
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
unsafe fn object_drop_front<E>(e: OwnedPtr<ErrorImpl<()>>, 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::<ErrorImpl<E>>().into_box() };
mem::forget(unerased._object)
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
unsafe fn object_ref<E>(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::<ErrorImpl<E>>().as_ref() }._object
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
unsafe fn object_mut<E>(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::<ErrorImpl<E>>().into_mut() }._object
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
unsafe fn object_boxed<E>(e: OwnedPtr<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>
where
E: StdError + Send + Sync + 'static,
{
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
unsafe { e.cast::<ErrorImpl<E>>().into_box() }
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
unsafe fn object_downcast<E>(e: RefPtr<'_, ErrorImpl<()>>, target: TypeId) -> Option<NonNull<()>>
where
E: 'static,
{
if TypeId::of::<E>() == target {
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a
// pointer to its E field.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().as_ref() };
Some(NonNull::from(&(unerased._object)).cast::<()>())
} else {
None
}
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
unsafe fn object_downcast_mut<E>(
e: MutPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
E: 'static,
{
if TypeId::of::<E>() == target {
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a
// pointer to its E field.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().into_mut() };
Some(NonNull::from(&mut (unerased._object)).cast::<()>())
} else {
None
}
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<ContextError<D, E>>.
@ -782,21 +643,6 @@ where
}
}
#[repr(C)]
pub(crate) struct ErrorHeader {
vtable: &'static ErrorVTable,
pub(crate) handler: Option<Box<dyn EyreHandler>>,
}
// repr C to ensure that E remains in the final position.
#[repr(C)]
pub(crate) struct ErrorImpl<E = ()> {
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<D, E> has the same layout as
// ContextError<ManuallyDrop<D>, E> and ContextError<D, ManuallyDrop<E>>.
#[repr(C)]
@ -805,91 +651,6 @@ pub(crate) struct ContextError<D, E> {
pub(crate) error: E,
}
impl<E> ErrorImpl<E> {
/// 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<E> StdError for ErrorImpl<E>
where
E: StdError,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
ErrorImpl::<()>::error(self.erase()).source()
}
}
impl<E> Debug for ErrorImpl<E>
where
E: Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
ErrorImpl::debug(self.erase(), formatter)
}
}
impl<E> Display for ErrorImpl<E>
where
E: Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(ErrorImpl::error(self.erase()), formatter)
}
}
impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
fn from(error: Report) -> Self {
let outer = ManuallyDrop::new(error);
unsafe {
// Read Box<ErrorImpl<()>> from error. Can't move it out because
// Report has a Drop impl which we want to not run.
// Use vtable to attach ErrorImpl<E>'s native StdError vtable for
// the right original type E.
(header(outer.inner.as_ref()).vtable.object_boxed)(outer.inner)
}
}
}
impl From<Report> for Box<dyn StdError + 'static> {
fn from(error: Report) -> Self {
Box::<dyn StdError + Send + Sync>::from(error)

View File

@ -1,4 +1,4 @@
use crate::{error::ErrorImpl, ptr::RefPtr};
use crate::{ptr::RefPtr, vtable::ErrorImpl};
use core::fmt;
impl ErrorImpl<()> {

View File

@ -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)]

230
eyre/src/vtable.rs Normal file
View File

@ -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<ErrorImpl<()>>),
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<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>,
pub(crate) object_downcast: unsafe fn(RefPtr<'_, ErrorImpl<()>>, TypeId) -> Option<NonNull<()>>,
pub(crate) object_downcast_mut:
unsafe fn(MutPtr<'_, ErrorImpl<()>>, TypeId) -> Option<NonNull<()>>,
pub(crate) object_drop_rest: unsafe fn(OwnedPtr<ErrorImpl<()>>, TypeId),
}
// repr C to ensure that E remains in the final position.
#[repr(C)]
pub(crate) struct ErrorImpl<E = ()> {
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<Box<dyn EyreHandler>>,
}
// 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<E> ErrorImpl<E> {
/// 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<E> StdError for ErrorImpl<E>
where
E: StdError,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
ErrorImpl::<()>::error(self.erase()).source()
}
}
impl<E> Debug for ErrorImpl<E>
where
E: Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
ErrorImpl::debug(self.erase(), formatter)
}
}
impl<E> Display for ErrorImpl<E>
where
E: Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(ErrorImpl::error(self.erase()), formatter)
}
}
impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
fn from(error: Report) -> Self {
let outer = ManuallyDrop::new(error);
unsafe {
// Read Box<ErrorImpl<()>> from error. Can't move it out because
// Report has a Drop impl which we want to not run.
// Use vtable to attach ErrorImpl<E>'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<E>.
pub(crate) unsafe fn object_drop<E>(e: OwnedPtr<ErrorImpl<()>>) {
// Cast to a context type and drop the Box allocation.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().into_box() };
drop(unerased);
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_drop_front<E>(e: OwnedPtr<ErrorImpl<()>>, 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::<ErrorImpl<E>>().into_box() };
mem::forget(unerased._object)
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_ref<E>(
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::<ErrorImpl<E>>().as_ref() }._object
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_mut<E>(
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::<ErrorImpl<E>>().into_mut() }._object
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_boxed<E>(
e: OwnedPtr<ErrorImpl<()>>,
) -> Box<dyn StdError + Send + Sync + 'static>
where
E: StdError + Send + Sync + 'static,
{
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
unsafe { e.cast::<ErrorImpl<E>>().into_box() }
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_downcast<E>(
e: RefPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
E: 'static,
{
if TypeId::of::<E>() == target {
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a
// pointer to its E field.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().as_ref() };
Some(NonNull::from(&(unerased._object)).cast::<()>())
} else {
None
}
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_downcast_mut<E>(
e: MutPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
E: 'static,
{
if TypeId::of::<E>() == target {
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a
// pointer to its E field.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().into_mut() };
Some(NonNull::from(&mut (unerased._object)).cast::<()>())
} else {
None
}
}