Fix miri validation errors through now stricter provenance (#103)

* fix(miri): box transmute and invalid references

The general causes for the miri invalidation is the prevelant use of
`Box` and its references to `ErrorImpl<()>`.

`mem::transmute` does not preserve the tag stack for transmuting the
boxes.

Additionally, having references to `ErrorImpl<()>` which has a different
layout than the allocation or `ErrorImpl<E>` for some unknown `E`. This
causes the new "untyped" reference to now have a provenance that
includes the size of E and thus is outside the provenance.

* fix(miri): downcast_mut using `&mut _ => *const _ => *mut`

* fix(miri): stub file reading

* fix(miri): don't construct temp references of shunk provenance

* ci: miri

* fix: `unsafe_op_in_unsafe_fn`

* chore!: bump MSRV

* chore: address PR comments

* fix: ci workflow names

* chore: raise msrv to 1.65 (addr2line)

* chore: revert distinctive CI names due to branch protection rules

The new names, such as `Test Platform Matrix` which do make it easier to
see which jobs failed, rather than msrv, test, and miri all being called
`Test Suite`, the in-place branch protection rules wait forever until
the now non-existent `Test Suite` passes
This commit is contained in:
Tei Leelo Roberts 2023-09-19 22:02:20 +02:00 committed by GitHub
parent 2d984da845
commit 9f4ecc497e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 470 additions and 148 deletions

View File

@ -6,6 +6,9 @@ on:
name: Continuous integration
env:
MIRIFLAGS: -Zmiri-strict-provenance
jobs:
check:
name: Check
@ -66,7 +69,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.42
toolchain: 1.65
override: true
- uses: actions-rs/cargo@v1
with:
@ -126,3 +129,17 @@ jobs:
with:
command: clippy
args: -- -D warnings
miri:
name: Miri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: miri
override: true
- uses: actions-rs/cargo@v1
with:
command: miri
args: test

View File

@ -27,7 +27,7 @@ pyo3 = { version = "0.13", default-features = false, features = ["auto-initializ
[dependencies]
indenter = "0.3.0"
once_cell = "1.4.0"
once_cell = "1.18.0"
pyo3 = { version = "0.13", optional = true, default-features = false }
[package.metadata.docs.rs]

View File

@ -1,4 +1,4 @@
use crate::error::ContextError;
use crate::error::{ContextError, ErrorImpl};
use crate::{ContextCompat, Report, StdError, WrapErr};
use core::fmt::{self, Debug, Display, Write};
@ -158,7 +158,7 @@ where
D: Display,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.error.inner.error())
Some(ErrorImpl::error(self.error.inner.as_ref()))
}
}

View File

@ -1,4 +1,5 @@
use crate::chain::Chain;
use crate::ptr::{MutPtr, OwnedPtr, RefPtr};
use crate::EyreHandler;
use crate::{Report, StdError};
use core::any::TypeId;
@ -70,6 +71,7 @@ impl Report {
}
#[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,
@ -80,6 +82,7 @@ impl Report {
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>,
};
@ -102,6 +105,7 @@ impl Report {
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>,
};
@ -125,6 +129,7 @@ impl Report {
object_mut: object_mut::<DisplayError<M>>,
object_boxed: object_boxed::<DisplayError<M>>,
object_downcast: object_downcast::<M>,
object_downcast_mut: object_downcast_mut::<M>,
object_drop_rest: object_drop_front::<M>,
};
@ -149,6 +154,7 @@ impl Report {
object_mut: object_mut::<ContextError<D, E>>,
object_boxed: object_boxed::<ContextError<D, E>>,
object_downcast: context_downcast::<D, E>,
object_downcast_mut: context_downcast_mut::<D, E>,
object_drop_rest: context_drop_rest::<D, E>,
};
@ -170,6 +176,7 @@ impl Report {
object_mut: object_mut::<BoxedError>,
object_boxed: object_boxed::<BoxedError>,
object_downcast: object_downcast::<Box<dyn StdError + Send + Sync>>,
object_downcast_mut: object_downcast_mut::<Box<dyn StdError + Send + Sync>>,
object_drop_rest: object_drop_front::<Box<dyn StdError + Send + Sync>>,
};
@ -191,20 +198,20 @@ impl Report {
where
E: StdError + Send + Sync + 'static,
{
let inner = Box::new(ErrorImpl {
vtable,
handler,
let inner = ErrorImpl {
header: ErrorHeader { vtable, handler },
_object: error,
});
// Erase the concrete type of E from the compile-time type system. This
// is equivalent to the safe unsize coersion from Box<ErrorImpl<E>> to
// Box<ErrorImpl<dyn StdError + Send + Sync + 'static>> except that the
// result is a thin pointer. The necessary behavior for manipulating the
// underlying ErrorImpl<E> is preserved in the vtable provided by the
// caller rather than a builtin fat pointer vtable.
let erased = mem::transmute::<Box<ErrorImpl<E>>, Box<ErrorImpl<()>>>(inner);
let inner = ManuallyDrop::new(erased);
Report { inner }
};
// Construct a new owned allocation through a raw pointer
//
// This does not keep the allocation around as a `Box` which would invalidate an
// references when moved
let ptr = OwnedPtr::<ErrorImpl<E>>::new(inner);
// Safety: the type
let ptr = ptr.cast::<ErrorImpl<()>>();
Report { inner: ptr }
}
/// Create a new error from an error message to wrap the existing error.
@ -264,7 +271,11 @@ impl Report {
where
D: Display + Send + Sync + 'static,
{
let handler = self.inner.handler.take();
// Safety: this access a `ErrorImpl<unknown>` as a valid reference to a `ErrorImpl<()>`
//
// As the generic is at the end of the struct and the struct is `repr(C)` this reference
// will be within bounds of the original pointer, and the field will have the same offset
let handler = header_mut(self.inner.as_mut()).handler.take();
let error: ContextError<D, Report> = ContextError { msg, error: self };
let vtable = &ErrorVTable {
@ -273,6 +284,7 @@ impl Report {
object_mut: object_mut::<ContextError<D, Report>>,
object_boxed: object_boxed::<ContextError<D, Report>>,
object_downcast: context_chain_downcast::<D>,
object_downcast_mut: context_chain_downcast_mut::<D>,
object_drop_rest: context_chain_drop_rest::<D>,
};
@ -280,6 +292,11 @@ impl Report {
unsafe { Report::construct(error, vtable, handler) }
}
/// Access the vtable for the current error object.
fn vtable(&self) -> &'static ErrorVTable {
header(self.inner.as_ref()).vtable
}
/// An iterator of the chain of source errors contained by this Report.
///
/// This iterator will visit every error in the cause chain of this error
@ -302,7 +319,7 @@ impl Report {
/// }
/// ```
pub fn chain(&self) -> Chain<'_> {
self.inner.chain()
ErrorImpl::chain(self.inner.as_ref())
}
/// The lowest level cause of this error &mdash; this error's cause's
@ -342,7 +359,7 @@ impl Report {
unsafe {
// Use vtable to find NonNull<()> which points to a value of type E
// somewhere inside the data structure.
let addr = match (self.inner.vtable.object_downcast)(&self.inner, target) {
let addr = match (self.vtable().object_downcast)(self.inner.as_ref(), target) {
Some(addr) => addr,
None => return Err(self),
};
@ -357,10 +374,9 @@ impl Report {
// Read Box<ErrorImpl<()>> from self. Can't move it out because
// Report has a Drop impl which we want to not run.
let inner = ptr::read(&outer.inner);
let erased = ManuallyDrop::into_inner(inner);
// Drop rest of the data structure outside of E.
(erased.vtable.object_drop_rest)(erased, target);
(outer.vtable().object_drop_rest)(inner, target);
Ok(error)
}
@ -413,8 +429,8 @@ impl Report {
unsafe {
// Use vtable to find NonNull<()> which points to a value of type E
// somewhere inside the data structure.
let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?;
Some(&*addr.cast::<E>().as_ptr())
let addr = (self.vtable().object_downcast)(self.inner.as_ref(), target)?;
Some(addr.cast::<E>().as_ref())
}
}
@ -427,31 +443,47 @@ impl Report {
unsafe {
// Use vtable to find NonNull<()> which points to a value of type E
// somewhere inside the data structure.
let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?;
Some(&mut *addr.cast::<E>().as_ptr())
let addr = (self.vtable().object_downcast_mut)(self.inner.as_mut(), target)?;
Some(addr.cast::<E>().as_mut())
}
}
/// Get a reference to the Handler for this Report.
pub fn handler(&self) -> &dyn EyreHandler {
self.inner.handler.as_ref().unwrap().as_ref()
header(self.inner.as_ref())
.handler
.as_ref()
.unwrap()
.as_ref()
}
/// Get a mutable reference to the Handler for this Report.
pub fn handler_mut(&mut self) -> &mut dyn EyreHandler {
self.inner.handler.as_mut().unwrap().as_mut()
header_mut(self.inner.as_mut())
.handler
.as_mut()
.unwrap()
.as_mut()
}
/// Get a reference to the Handler for this Report.
#[doc(hidden)]
pub fn context(&self) -> &dyn EyreHandler {
self.inner.handler.as_ref().unwrap().as_ref()
header(self.inner.as_ref())
.handler
.as_ref()
.unwrap()
.as_ref()
}
/// Get a mutable reference to the Handler for this Report.
#[doc(hidden)]
pub fn context_mut(&mut self) -> &mut dyn EyreHandler {
self.inner.handler.as_mut().unwrap().as_mut()
header_mut(self.inner.as_mut())
.handler
.as_mut()
.unwrap()
.as_mut()
}
}
@ -469,25 +501,25 @@ impl Deref for Report {
type Target = dyn StdError + Send + Sync + 'static;
fn deref(&self) -> &Self::Target {
self.inner.error()
ErrorImpl::error(self.inner.as_ref())
}
}
impl DerefMut for Report {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.error_mut()
ErrorImpl::error_mut(self.inner.as_mut())
}
}
impl Display for Report {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.display(formatter)
ErrorImpl::display(self.inner.as_ref(), formatter)
}
}
impl Debug for Report {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.debug(formatter)
ErrorImpl::debug(self.inner.as_ref(), formatter)
}
}
@ -495,39 +527,35 @@ impl Drop for Report {
fn drop(&mut self) {
unsafe {
// Read Box<ErrorImpl<()>> from self.
let inner = ptr::read(&self.inner);
let erased = ManuallyDrop::into_inner(inner);
// Invoke the vtable's drop behavior.
(erased.vtable.object_drop)(erased);
(self.vtable().object_drop)(self.inner);
}
}
}
struct ErrorVTable {
object_drop: unsafe fn(Box<ErrorImpl<()>>),
object_ref: unsafe fn(&ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static),
object_mut: unsafe fn(&mut ErrorImpl<()>) -> &mut (dyn StdError + Send + Sync + 'static),
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(Box<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>,
object_downcast: unsafe fn(&ErrorImpl<()>, TypeId) -> Option<NonNull<()>>,
object_drop_rest: unsafe fn(Box<ErrorImpl<()>>, TypeId),
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: Box<ErrorImpl<()>>) {
// Cast back to ErrorImpl<E> so that the allocator receives the correct
// Layout to deallocate the Box's memory.
// 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.md
let unerased: Box<ErrorImpl<E>> = Box::from_raw(Box::into_raw(e).cast());
/// # 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: Box<ErrorImpl<()>>, target: TypeId) {
/// # 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.
@ -536,74 +564,132 @@ unsafe fn object_drop_front<E>(e: Box<ErrorImpl<()>>, target: TypeId) {
// 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: Box<ErrorImpl<ManuallyDrop<E>>> = Box::from_raw(Box::into_raw(e).cast());
drop(unerased);
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: &ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static)
/// # 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.
&(*(e as *const ErrorImpl<()> as *const ErrorImpl<E>))._object
&unsafe { e.cast::<ErrorImpl<E>>().as_ref() }._object
}
// Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_mut<E>(e: &mut ErrorImpl<()>) -> &mut (dyn StdError + Send + Sync + 'static)
/// # 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 (*(e as *mut ErrorImpl<()> as *mut ErrorImpl<E>))._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: Box<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>
/// # 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.
mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e)
unsafe { e.cast::<ErrorImpl<E>>().into_box() }
}
// Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_downcast<E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
/// # 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 = e as *const ErrorImpl<()> as *const ErrorImpl<E>;
let addr = &(*unerased)._object as *const E as *mut ();
Some(NonNull::new_unchecked(addr))
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<ContextError<D, E>>.
unsafe fn context_downcast<D, E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
/// # 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>>.
unsafe fn context_downcast<D, E>(
e: RefPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
D: 'static,
E: 'static,
{
if TypeId::of::<D>() == target {
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>;
let addr = &(*unerased)._object.msg as *const D as *mut ();
Some(NonNull::new_unchecked(addr))
let unerased = unsafe { e.cast::<ErrorImpl<ContextError<D, E>>>().as_ref() };
let addr = NonNull::from(&unerased._object.msg).cast::<()>();
Some(addr)
} else if TypeId::of::<E>() == target {
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>;
let addr = &(*unerased)._object.error as *const E as *mut ();
Some(NonNull::new_unchecked(addr))
let unerased = unsafe { e.cast::<ErrorImpl<ContextError<D, E>>>().as_ref() };
let addr = NonNull::from(&unerased._object.error).cast::<()>();
Some(addr)
} else {
None
}
}
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>.
unsafe fn context_drop_rest<D, E>(e: Box<ErrorImpl<()>>, target: TypeId)
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<ContextError<D, E>>.
unsafe fn context_downcast_mut<D, E>(
e: MutPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
D: 'static,
E: 'static,
{
if TypeId::of::<D>() == target {
let unerased = unsafe { e.cast::<ErrorImpl<ContextError<D, E>>>().into_mut() };
let addr = NonNull::from(&unerased._object.msg).cast::<()>();
Some(addr)
} else if TypeId::of::<E>() == target {
let unerased = unsafe { e.cast::<ErrorImpl<ContextError<D, E>>>().into_mut() };
let addr = NonNull::from(&mut unerased._object.error).cast::<()>();
Some(addr)
} else {
None
}
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<ContextError<D, E>>.
unsafe fn context_drop_rest<D, E>(e: OwnedPtr<ErrorImpl<()>>, target: TypeId)
where
D: 'static,
E: 'static,
@ -611,69 +697,101 @@ where
// Called after downcasting by value to either the D or the E and doing a
// ptr::read to take ownership of that value.
if TypeId::of::<D>() == target {
let unerased = mem::transmute::<
Box<ErrorImpl<()>>,
Box<ErrorImpl<ContextError<ManuallyDrop<D>, E>>>,
>(e);
drop(unerased);
unsafe {
e.cast::<ErrorImpl<ContextError<ManuallyDrop<E>, E>>>()
.into_box()
};
} else {
let unerased = mem::transmute::<
Box<ErrorImpl<()>>,
Box<ErrorImpl<ContextError<D, ManuallyDrop<E>>>>,
>(e);
drop(unerased);
debug_assert_eq!(TypeId::of::<E>(), target);
unsafe {
e.cast::<ErrorImpl<ContextError<E, ManuallyDrop<E>>>>()
.into_box()
};
}
}
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
unsafe fn context_chain_downcast<D>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
unsafe fn context_chain_downcast<D>(
e: RefPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
D: 'static,
{
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, Report>>;
let unerased = unsafe { e.cast::<ErrorImpl<ContextError<D, Report>>>().as_ref() };
if TypeId::of::<D>() == target {
let addr = &(*unerased)._object.msg as *const D as *mut ();
Some(NonNull::new_unchecked(addr))
let addr = NonNull::from(&unerased._object.msg).cast::<()>();
Some(addr)
} else {
// Recurse down the context chain per the inner error's vtable.
let source = &(*unerased)._object.error;
(source.inner.vtable.object_downcast)(&source.inner, target)
let source = &unerased._object.error;
unsafe { (source.vtable().object_downcast)(source.inner.as_ref(), target) }
}
}
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
unsafe fn context_chain_drop_rest<D>(e: Box<ErrorImpl<()>>, target: TypeId)
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
unsafe fn context_chain_downcast_mut<D>(
e: MutPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
D: 'static,
{
let unerased = unsafe { e.cast::<ErrorImpl<ContextError<D, Report>>>().into_mut() };
if TypeId::of::<D>() == target {
let addr = NonNull::from(&unerased._object.msg).cast::<()>();
Some(addr)
} else {
// Recurse down the context chain per the inner error's vtable.
let source = &mut unerased._object.error;
unsafe { (source.vtable().object_downcast_mut)(source.inner.as_mut(), target) }
}
}
/// # Safety
///
/// Requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
unsafe fn context_chain_drop_rest<D>(e: OwnedPtr<ErrorImpl<()>>, target: TypeId)
where
D: 'static,
{
// Called after downcasting by value to either the D or one of the causes
// and doing a ptr::read to take ownership of that value.
if TypeId::of::<D>() == target {
let unerased = mem::transmute::<
Box<ErrorImpl<()>>,
Box<ErrorImpl<ContextError<ManuallyDrop<D>, Report>>>,
>(e);
let unerased = unsafe {
e.cast::<ErrorImpl<ContextError<ManuallyDrop<D>, Report>>>()
.into_box()
};
// Drop the entire rest of the data structure rooted in the next Report.
drop(unerased);
} else {
let unerased = mem::transmute::<
Box<ErrorImpl<()>>,
Box<ErrorImpl<ContextError<D, ManuallyDrop<Report>>>>,
>(e);
unsafe {
let unerased = e
.cast::<ErrorImpl<ContextError<D, ManuallyDrop<Report>>>>()
.into_box();
// Read out a ManuallyDrop<Box<ErrorImpl<()>>> from the next error.
let inner = ptr::read(&unerased._object.error.inner);
let inner = ptr::read(&unerased.as_ref()._object.error.inner);
drop(unerased);
let erased = ManuallyDrop::into_inner(inner);
// Recursively drop the next error using the same target typeid.
(erased.vtable.object_drop_rest)(erased, target);
(header(inner.as_ref()).vtable.object_drop_rest)(inner, target);
}
}
}
#[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> {
vtable: &'static ErrorVTable,
pub(crate) handler: Option<Box<dyn EyreHandler>>,
pub(crate) struct ErrorImpl<E = ()> {
header: ErrorHeader,
// NOTE: Don't use directly. Use only through vtable. Erased type may have
// different alignment.
_object: E,
@ -688,29 +806,47 @@ pub(crate) struct ContextError<D, E> {
}
impl<E> ErrorImpl<E> {
fn erase(&self) -> &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.
unsafe { &*(self as *const ErrorImpl<E> as *const ErrorImpl<()>) }
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(&self) -> &(dyn StdError + Send + Sync + 'static) {
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 { &*(self.vtable.object_ref)(self) }
unsafe { (header(this).vtable.object_ref)(this) }
}
pub(crate) fn error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
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 { &mut *(self.vtable.object_mut)(self) }
unsafe { (header_mut(this).vtable.object_mut)(this) }
}
pub(crate) fn chain(&self) -> Chain<'_> {
Chain::new(self.error())
pub(crate) fn chain(this: RefPtr<'_, Self>) -> Chain<'_> {
Chain::new(Self::error(this))
}
pub(crate) fn header(this: RefPtr<'_, ErrorImpl>) -> &ErrorHeader {
header(this)
}
}
@ -719,7 +855,7 @@ where
E: StdError,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.erase().error().source()
ErrorImpl::<()>::error(self.erase()).source()
}
}
@ -728,7 +864,7 @@ where
E: Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.erase().debug(formatter)
ErrorImpl::debug(self.erase(), formatter)
}
}
@ -737,7 +873,7 @@ where
E: Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.erase().error(), formatter)
Display::fmt(ErrorImpl::error(self.erase()), formatter)
}
}
@ -747,12 +883,9 @@ impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
unsafe {
// Read Box<ErrorImpl<()>> from error. Can't move it out because
// Report has a Drop impl which we want to not run.
let inner = ptr::read(&outer.inner);
let erased = ManuallyDrop::into_inner(inner);
// Use vtable to attach ErrorImpl<E>'s native StdError vtable for
// the right original type E.
(erased.vtable.object_boxed)(erased)
(header(outer.inner.as_ref()).vtable.object_boxed)(outer.inner)
}
}
}

View File

@ -1,18 +1,21 @@
use crate::error::ErrorImpl;
use crate::{error::ErrorImpl, ptr::RefPtr};
use core::fmt;
impl ErrorImpl<()> {
pub(crate) fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.handler
pub(crate) fn display(this: RefPtr<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
ErrorImpl::header(this)
.handler
.as_ref()
.map(|handler| handler.display(self.error(), f))
.unwrap_or_else(|| core::fmt::Display::fmt(self.error(), f))
.map(|handler| handler.display(Self::error(this), f))
.unwrap_or_else(|| core::fmt::Display::fmt(Self::error(this), f))
}
pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.handler
/// Debug formats the error using the captured handler
pub(crate) fn debug(this: RefPtr<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
ErrorImpl::header(this)
.handler
.as_ref()
.map(|handler| handler.debug(self.error(), f))
.unwrap_or_else(|| core::fmt::Debug::fmt(self.error(), f))
.map(|handler| handler.debug(Self::error(this), f))
.unwrap_or_else(|| core::fmt::Debug::fmt(Self::error(this), f))
}
}

View File

@ -334,6 +334,7 @@
missing_docs,
// FIXME: this lint is currently nightly only
rustdoc::missing_doc_code_examples,
unsafe_op_in_unsafe_fn,
rust_2018_idioms,
unreachable_pub,
bad_style,
@ -370,12 +371,12 @@ mod error;
mod fmt;
mod kind;
mod macros;
mod ptr;
mod wrapper;
use crate::backtrace::Backtrace;
use crate::error::ErrorImpl;
use core::fmt::Display;
use core::mem::ManuallyDrop;
use std::error::Error as StdError;
@ -383,6 +384,7 @@ pub use eyre as format_err;
/// Compatibility re-export of `eyre` for interop with `anyhow`
pub use eyre as anyhow;
use once_cell::sync::OnceCell;
use ptr::OwnedPtr;
#[doc(hidden)]
pub use DefaultHandler as DefaultContext;
#[doc(hidden)]
@ -469,7 +471,7 @@ pub use WrapErr as Context;
/// [`hook`]: fn.set_hook.html
#[must_use]
pub struct Report {
inner: ManuallyDrop<Box<ErrorImpl<()>>>,
inner: OwnedPtr<ErrorImpl<()>>,
}
type ErrorHook =

149
src/ptr.rs Normal file
View File

@ -0,0 +1,149 @@
use std::{marker::PhantomData, ptr::NonNull};
/// An owned pointer
///
/// **NOTE**: Does not deallocate when dropped
pub(crate) struct OwnedPtr<T: ?Sized> {
ptr: NonNull<T>,
}
impl<T: ?Sized> Copy for OwnedPtr<T> {}
impl<T: ?Sized> Clone for OwnedPtr<T> {
fn clone(&self) -> Self {
*self
}
}
unsafe impl<T> Send for OwnedPtr<T> where T: Send {}
unsafe impl<T> Sync for OwnedPtr<T> where T: Send {}
impl<T> OwnedPtr<T> {
pub(crate) fn new(value: T) -> Self {
Self::from_boxed(Box::new(value))
}
pub(crate) fn from_boxed(boxed: Box<T>) -> Self {
// Safety: `Box::into_raw` is guaranteed to be non-null
Self {
ptr: unsafe { NonNull::new_unchecked(Box::into_raw(boxed)) },
}
}
/// Convert the pointer to another type
pub(crate) fn cast<U>(self) -> OwnedPtr<U> {
OwnedPtr {
ptr: self.ptr.cast(),
}
}
/// Context the pointer into a Box
///
/// # Safety
///
/// Dropping the Box will deallocate a layout of `T` and run the destructor of `T`.
///
/// A cast pointer must therefore be cast back to the original type before calling this method.
pub(crate) unsafe fn into_box(self) -> Box<T> {
unsafe { Box::from_raw(self.ptr.as_ptr()) }
}
pub(crate) const fn as_ref(&self) -> RefPtr<'_, T> {
RefPtr {
ptr: self.ptr,
_marker: PhantomData,
}
}
pub(crate) fn as_mut(&mut self) -> MutPtr<'_, T> {
MutPtr {
ptr: self.ptr,
_marker: PhantomData,
}
}
}
/// Convenience lifetime annotated mutable pointer which facilitates returning an inferred lifetime
/// in a `fn` pointer.
pub(crate) struct RefPtr<'a, T: ?Sized> {
pub(crate) ptr: NonNull<T>,
_marker: PhantomData<&'a T>,
}
/// Safety: RefPtr indicates a shared reference to a value and as such exhibits the same Send +
/// Sync behavior of &'a T
unsafe impl<'a, T: ?Sized> Send for RefPtr<'a, T> where &'a T: Send {}
unsafe impl<'a, T: ?Sized> Sync for RefPtr<'a, T> where &'a T: Sync {}
impl<'a, T: ?Sized> Copy for RefPtr<'a, T> {}
impl<'a, T: ?Sized> Clone for RefPtr<'a, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, T: ?Sized> RefPtr<'a, T> {
pub(crate) fn new(ptr: &'a T) -> Self {
Self {
ptr: NonNull::from(ptr),
_marker: PhantomData,
}
}
/// Convert the pointer to another type
pub(crate) fn cast<U>(self) -> RefPtr<'a, U> {
RefPtr {
ptr: self.ptr.cast(),
_marker: PhantomData,
}
}
/// Returns a shared reference to the owned value
///
/// # Safety
///
/// See: [`NonNull::as_ref`]
#[inline]
pub(crate) unsafe fn as_ref(&self) -> &'a T {
unsafe { self.ptr.as_ref() }
}
}
/// Convenience lifetime annotated mutable pointer which facilitates returning an inferred lifetime
/// in a `fn` pointer.
pub(crate) struct MutPtr<'a, T: ?Sized> {
pub(crate) ptr: NonNull<T>,
_marker: PhantomData<&'a mut T>,
}
/// Safety: RefPtr indicates an exclusive reference to a value and as such exhibits the same Send +
/// Sync behavior of &'a mut T
unsafe impl<'a, T: ?Sized> Send for MutPtr<'a, T> where &'a mut T: Send {}
unsafe impl<'a, T: ?Sized> Sync for MutPtr<'a, T> where &'a mut T: Sync {}
impl<'a, T: ?Sized> Copy for MutPtr<'a, T> {}
impl<'a, T: ?Sized> Clone for MutPtr<'a, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, T: ?Sized> MutPtr<'a, T> {
/// Convert the pointer to another type
pub(crate) fn cast<U>(self) -> MutPtr<'a, U> {
MutPtr {
ptr: self.ptr.cast(),
_marker: PhantomData,
}
}
/// Returns a mutable reference to the owned value with the lifetime decoupled from self
///
/// # Safety
///
/// See: [`NonNull::as_mut`]
#[inline]
pub(crate) unsafe fn into_mut(mut self) -> &'a mut T {
unsafe { self.ptr.as_mut() }
}
}

View File

@ -5,6 +5,9 @@ use core::fmt::{self, Debug, Display};
pub(crate) struct DisplayError<M>(pub(crate) M);
#[repr(transparent)]
/// Wraps a Debug + Display type as an error.
///
/// Its Debug and Display impls are the same as the wrapped type.
pub(crate) struct MessageError<M>(pub(crate) M);
pub(crate) struct NoneError;

View File

@ -1,4 +1,5 @@
#[rustversion::attr(not(nightly), ignore)]
#[cfg_attr(miri, ignore)]
#[test]
fn ui() {
let t = trybuild::TestCases::new();

View File

@ -46,7 +46,7 @@ fn test_wrap_err() {
Box::new(LocationHandler::new(expected_location))
}));
let err = std::fs::read_to_string("totally_fake_path")
let err = read_path("totally_fake_path")
.wrap_err("oopsie")
.unwrap_err();
@ -54,6 +54,20 @@ fn test_wrap_err() {
println!("{:?}", err);
}
#[cfg(not(miri))]
fn read_path(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path)
}
#[cfg(miri)]
fn read_path(_path: &str) -> Result<String, std::io::Error> {
// Miri doesn't support reading files, so we just return an error
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Miri doesn't support reading files",
))
}
#[test]
fn test_wrap_err_with() {
let _ = eyre::set_hook(Box::new(|_e| {
@ -61,7 +75,7 @@ fn test_wrap_err_with() {
Box::new(LocationHandler::new(expected_location))
}));
let err = std::fs::read_to_string("totally_fake_path")
let err = read_path("totally_fake_path")
.wrap_err_with(|| "oopsie")
.unwrap_err();
@ -76,7 +90,7 @@ fn test_context() {
Box::new(LocationHandler::new(expected_location))
}));
let err = std::fs::read_to_string("totally_fake_path")
let err = read_path("totally_fake_path")
.context("oopsie")
.unwrap_err();
@ -91,7 +105,7 @@ fn test_with_context() {
Box::new(LocationHandler::new(expected_location))
}));
let err = std::fs::read_to_string("totally_fake_path")
let err = read_path("totally_fake_path")
.with_context(|| "oopsie")
.unwrap_err();