fix(miri): Resolve Miri's concerns around unsafe code (#197)

This commit is contained in:
Nathan Whitaker 2022-08-25 08:30:18 -07:00 committed by GitHub
parent 12279f83a8
commit 5f3429b062
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 389 additions and 154 deletions

View File

@ -45,6 +45,24 @@ jobs:
- name: Run tests - name: Run tests
run: cargo test --all --verbose --features fancy run: cargo test --all --verbose --features fancy
miri:
name: Miri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
components: miri,rust-src
override: true
- name: Run tests with miri
env:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
run: cargo miri test --all --verbose --features fancy
minimal_versions: minimal_versions:
name: Minimal versions check name: Minimal versions check
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View File

@ -1,4 +1,4 @@
use super::error::ContextError; use super::error::{ContextError, ErrorImpl};
use super::{Report, WrapErr}; use super::{Report, WrapErr};
use core::fmt::{self, Debug, Display, Write}; use core::fmt::{self, Debug, Display, Write};
@ -116,7 +116,7 @@ where
D: Display, D: Display,
{ {
fn source(&self) -> Option<&(dyn StdError + 'static)> { fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.error.inner.error()) unsafe { Some(ErrorImpl::error(self.error.inner.by_ref())) }
} }
} }
@ -159,23 +159,23 @@ where
D: Display, D: Display,
{ {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.error.inner.diagnostic().code() unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).code() }
} }
fn severity(&self) -> Option<crate::Severity> { fn severity(&self) -> Option<crate::Severity> {
self.error.inner.diagnostic().severity() unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).severity() }
} }
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.error.inner.diagnostic().help() unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).help() }
} }
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.error.inner.diagnostic().url() unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).url() }
} }
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> { fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
self.error.inner.diagnostic().labels() unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).labels() }
} }
fn source_code(&self) -> Option<&dyn crate::SourceCode> { fn source_code(&self) -> Option<&dyn crate::SourceCode> {

View File

@ -1,9 +1,10 @@
use core::any::TypeId; use core::any::TypeId;
use core::fmt::{self, Debug, Display}; use core::fmt::{self, Debug, Display};
use core::mem::{self, ManuallyDrop}; use core::mem::ManuallyDrop;
use core::ptr::{self, NonNull}; use core::ptr::{self, NonNull};
use std::error::Error as StdError; use std::error::Error as StdError;
use super::ptr::{Mut, Own, Ref};
use super::Report; use super::Report;
use super::ReportHandler; use super::ReportHandler;
use crate::chain::Chain; use crate::chain::Chain;
@ -80,7 +81,6 @@ impl Report {
let vtable = &ErrorVTable { let vtable = &ErrorVTable {
object_drop: object_drop::<E>, object_drop: object_drop::<E>,
object_ref: object_ref::<E>, object_ref: object_ref::<E>,
object_mut: object_mut::<E>,
object_ref_stderr: object_ref_stderr::<E>, object_ref_stderr: object_ref_stderr::<E>,
object_boxed: object_boxed::<E>, object_boxed: object_boxed::<E>,
object_boxed_stderr: object_boxed_stderr::<E>, object_boxed_stderr: object_boxed_stderr::<E>,
@ -104,7 +104,6 @@ impl Report {
let vtable = &ErrorVTable { let vtable = &ErrorVTable {
object_drop: object_drop::<MessageError<M>>, object_drop: object_drop::<MessageError<M>>,
object_ref: object_ref::<MessageError<M>>, object_ref: object_ref::<MessageError<M>>,
object_mut: object_mut::<MessageError<M>>,
object_ref_stderr: object_ref_stderr::<MessageError<M>>, object_ref_stderr: object_ref_stderr::<MessageError<M>>,
object_boxed: object_boxed::<MessageError<M>>, object_boxed: object_boxed::<MessageError<M>>,
object_boxed_stderr: object_boxed_stderr::<MessageError<M>>, object_boxed_stderr: object_boxed_stderr::<MessageError<M>>,
@ -130,7 +129,6 @@ impl Report {
let vtable = &ErrorVTable { let vtable = &ErrorVTable {
object_drop: object_drop::<ContextError<D, E>>, object_drop: object_drop::<ContextError<D, E>>,
object_ref: object_ref::<ContextError<D, E>>, object_ref: object_ref::<ContextError<D, E>>,
object_mut: object_mut::<ContextError<D, E>>,
object_ref_stderr: object_ref_stderr::<ContextError<D, E>>, object_ref_stderr: object_ref_stderr::<ContextError<D, E>>,
object_boxed: object_boxed::<ContextError<D, E>>, object_boxed: object_boxed::<ContextError<D, E>>,
object_boxed_stderr: object_boxed_stderr::<ContextError<D, E>>, object_boxed_stderr: object_boxed_stderr::<ContextError<D, E>>,
@ -153,7 +151,6 @@ impl Report {
let vtable = &ErrorVTable { let vtable = &ErrorVTable {
object_drop: object_drop::<BoxedError>, object_drop: object_drop::<BoxedError>,
object_ref: object_ref::<BoxedError>, object_ref: object_ref::<BoxedError>,
object_mut: object_mut::<BoxedError>,
object_ref_stderr: object_ref_stderr::<BoxedError>, object_ref_stderr: object_ref_stderr::<BoxedError>,
object_boxed: object_boxed::<BoxedError>, object_boxed: object_boxed::<BoxedError>,
object_boxed_stderr: object_boxed_stderr::<BoxedError>, object_boxed_stderr: object_boxed_stderr::<BoxedError>,
@ -190,8 +187,7 @@ impl Report {
// result is a thin pointer. The necessary behavior for manipulating the // result is a thin pointer. The necessary behavior for manipulating the
// underlying ErrorImpl<E> is preserved in the vtable provided by the // underlying ErrorImpl<E> is preserved in the vtable provided by the
// caller rather than a builtin fat pointer vtable. // caller rather than a builtin fat pointer vtable.
let erased = mem::transmute::<Box<ErrorImpl<E>>, Box<ErrorImpl<()>>>(inner); let inner = Own::new(inner).cast::<ErasedErrorImpl>();
let inner = ManuallyDrop::new(erased);
Report { inner } Report { inner }
} }
@ -204,17 +200,16 @@ impl Report {
/// The primary reason to use `error.wrap_err(...)` instead of /// The primary reason to use `error.wrap_err(...)` instead of
/// `result.wrap_err(...)` via the `WrapErr` trait would be if the /// `result.wrap_err(...)` via the `WrapErr` trait would be if the
/// message needs to depend on some data held by the underlying error: /// message needs to depend on some data held by the underlying error:
pub fn wrap_err<D>(mut self, msg: D) -> Self pub fn wrap_err<D>(self, msg: D) -> Self
where where
D: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
{ {
let handler = self.inner.handler.take(); let handler = unsafe { self.inner.by_mut().deref_mut().handler.take() };
let error: ContextError<D, Report> = ContextError { msg, error: self }; let error: ContextError<D, Report> = ContextError { msg, error: self };
let vtable = &ErrorVTable { let vtable = &ErrorVTable {
object_drop: object_drop::<ContextError<D, Report>>, object_drop: object_drop::<ContextError<D, Report>>,
object_ref: object_ref::<ContextError<D, Report>>, object_ref: object_ref::<ContextError<D, Report>>,
object_mut: object_mut::<ContextError<D, Report>>,
object_ref_stderr: object_ref_stderr::<ContextError<D, Report>>, object_ref_stderr: object_ref_stderr::<ContextError<D, Report>>,
object_boxed: object_boxed::<ContextError<D, Report>>, object_boxed: object_boxed::<ContextError<D, Report>>,
object_boxed_stderr: object_boxed_stderr::<ContextError<D, Report>>, object_boxed_stderr: object_boxed_stderr::<ContextError<D, Report>>,
@ -256,7 +251,7 @@ impl Report {
/// } /// }
/// ``` /// ```
pub fn chain(&self) -> Chain<'_> { pub fn chain(&self) -> Chain<'_> {
self.inner.chain() unsafe { ErrorImpl::chain(self.inner.by_ref()) }
} }
/// The lowest level cause of this error &mdash; this error's cause's /// The lowest level cause of this error &mdash; this error's cause's
@ -265,12 +260,7 @@ impl Report {
/// The root cause is the last error in the iterator produced by /// The root cause is the last error in the iterator produced by
/// [`chain()`](Report::chain). /// [`chain()`](Report::chain).
pub fn root_cause(&self) -> &(dyn StdError + 'static) { pub fn root_cause(&self) -> &(dyn StdError + 'static) {
let mut chain = self.chain(); self.chain().last().unwrap()
let mut root_cause = chain.next().unwrap();
for cause in chain {
root_cause = cause;
}
root_cause
} }
/// Returns true if `E` is the type held by this error object. /// Returns true if `E` is the type held by this error object.
@ -294,11 +284,12 @@ impl Report {
E: Display + Debug + Send + Sync + 'static, E: Display + Debug + Send + Sync + 'static,
{ {
let target = TypeId::of::<E>(); let target = TypeId::of::<E>();
let inner = self.inner.by_mut();
unsafe { unsafe {
// Use vtable to find NonNull<()> which points to a value of type E // Use vtable to find NonNull<()> which points to a value of type E
// somewhere inside the data structure. // somewhere inside the data structure.
let addr = match (self.inner.vtable.object_downcast)(&self.inner, target) { let addr = match (vtable(inner.ptr).object_downcast)(inner.by_ref(), target) {
Some(addr) => addr, Some(addr) => addr.by_mut().extend(),
None => return Err(self), None => return Err(self),
}; };
@ -307,15 +298,10 @@ impl Report {
let outer = ManuallyDrop::new(self); let outer = ManuallyDrop::new(self);
// Read E from where the vtable found it. // Read E from where the vtable found it.
let error = ptr::read(addr.cast::<E>().as_ptr()); let error = addr.cast::<E>().read();
// 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. // Drop rest of the data structure outside of E.
(erased.vtable.object_drop_rest)(erased, target); (vtable(outer.inner.ptr).object_drop_rest)(outer.inner, target);
Ok(error) Ok(error)
} }
@ -365,8 +351,8 @@ impl Report {
unsafe { unsafe {
// Use vtable to find NonNull<()> which points to a value of type E // Use vtable to find NonNull<()> which points to a value of type E
// somewhere inside the data structure. // somewhere inside the data structure.
let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?; let addr = (vtable(self.inner.ptr).object_downcast)(self.inner.by_ref(), target)?;
Some(&*addr.cast::<E>().as_ptr()) Some(addr.cast::<E>().deref())
} }
} }
@ -379,19 +365,36 @@ impl Report {
unsafe { unsafe {
// Use vtable to find NonNull<()> which points to a value of type E // Use vtable to find NonNull<()> which points to a value of type E
// somewhere inside the data structure. // somewhere inside the data structure.
let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?; let addr =
Some(&mut *addr.cast::<E>().as_ptr()) (vtable(self.inner.ptr).object_downcast)(self.inner.by_ref(), target)?.by_mut();
Some(addr.cast::<E>().deref_mut())
} }
} }
/// Get a reference to the Handler for this Report. /// Get a reference to the Handler for this Report.
pub fn handler(&self) -> &dyn ReportHandler { pub fn handler(&self) -> &dyn ReportHandler {
self.inner.handler.as_ref().unwrap().as_ref() unsafe {
self.inner
.by_ref()
.deref()
.handler
.as_ref()
.unwrap()
.as_ref()
}
} }
/// Get a mutable reference to the Handler for this Report. /// Get a mutable reference to the Handler for this Report.
pub fn handler_mut(&mut self) -> &mut dyn ReportHandler { pub fn handler_mut(&mut self) -> &mut dyn ReportHandler {
self.inner.handler.as_mut().unwrap().as_mut() unsafe {
self.inner
.by_mut()
.deref_mut()
.handler
.as_mut()
.unwrap()
.as_mut()
}
} }
/// Provide source code for this error /// Provide source code for this error
@ -418,154 +421,160 @@ impl Deref for Report {
type Target = dyn Diagnostic + Send + Sync + 'static; type Target = dyn Diagnostic + Send + Sync + 'static;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.inner.diagnostic() unsafe { ErrorImpl::diagnostic(self.inner.by_ref()) }
} }
} }
impl DerefMut for Report { impl DerefMut for Report {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.diagnostic_mut() unsafe { ErrorImpl::diagnostic_mut(self.inner.by_mut()) }
} }
} }
impl Display for Report { impl Display for Report {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.display(formatter) unsafe { ErrorImpl::display(self.inner.by_ref(), formatter) }
} }
} }
impl Debug for Report { impl Debug for Report {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.debug(formatter) unsafe { ErrorImpl::debug(self.inner.by_ref(), formatter) }
} }
} }
impl Drop for Report { impl Drop for Report {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
// Read Box<ErrorImpl<()>> from self.
let inner = ptr::read(&self.inner);
let erased = ManuallyDrop::into_inner(inner);
// Invoke the vtable's drop behavior. // Invoke the vtable's drop behavior.
(erased.vtable.object_drop)(erased); (vtable(self.inner.ptr).object_drop)(self.inner);
} }
} }
} }
struct ErrorVTable { struct ErrorVTable {
object_drop: unsafe fn(Box<ErrorImpl<()>>), object_drop: unsafe fn(Own<ErasedErrorImpl>),
object_ref: unsafe fn(&ErrorImpl<()>) -> &(dyn Diagnostic + Send + Sync + 'static), object_ref:
object_mut: unsafe fn(&mut ErrorImpl<()>) -> &mut (dyn Diagnostic + Send + Sync + 'static), unsafe fn(Ref<'_, ErasedErrorImpl>) -> Ref<'_, dyn Diagnostic + Send + Sync + 'static>,
object_ref_stderr: unsafe fn(&ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static), object_ref_stderr:
unsafe fn(Ref<'_, ErasedErrorImpl>) -> Ref<'_, dyn StdError + Send + Sync + 'static>,
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
object_boxed: unsafe fn(Box<ErrorImpl<()>>) -> Box<dyn Diagnostic + Send + Sync + 'static>, object_boxed: unsafe fn(Own<ErasedErrorImpl>) -> Box<dyn Diagnostic + Send + Sync + 'static>,
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
object_boxed_stderr: unsafe fn(Box<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>, object_boxed_stderr:
object_downcast: unsafe fn(&ErrorImpl<()>, TypeId) -> Option<NonNull<()>>, unsafe fn(Own<ErasedErrorImpl>) -> Box<dyn StdError + Send + Sync + 'static>,
object_drop_rest: unsafe fn(Box<ErrorImpl<()>>, TypeId), object_downcast: unsafe fn(Ref<'_, ErasedErrorImpl>, TypeId) -> Option<Ref<'_, ()>>,
object_drop_rest: unsafe fn(Own<ErasedErrorImpl>, TypeId),
} }
// Safety: requires layout of *e to match ErrorImpl<E>. // Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_drop<E>(e: Box<ErrorImpl<()>>) { unsafe fn object_drop<E>(e: Own<ErasedErrorImpl>) {
// Cast back to ErrorImpl<E> so that the allocator receives the correct // Cast back to ErrorImpl<E> so that the allocator receives the correct
// Layout to deallocate the Box's memory. // Layout to deallocate the Box's memory.
let unerased = mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e); let unerased = e.cast::<ErrorImpl<E>>().boxed();
drop(unerased); drop(unerased);
} }
// Safety: requires layout of *e to match ErrorImpl<E>. // Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_drop_front<E>(e: Box<ErrorImpl<()>>, target: TypeId) { unsafe fn object_drop_front<E>(e: Own<ErasedErrorImpl>, target: TypeId) {
// Drop the fields of ErrorImpl other than E as well as the Box allocation, // 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 // without dropping E itself. This is used by downcast after doing a
// ptr::read to take ownership of the E. // ptr::read to take ownership of the E.
let _ = target; let _ = target;
let unerased = mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<ManuallyDrop<E>>>>(e); let unerased = e.cast::<ErrorImpl<ManuallyDrop<E>>>().boxed();
drop(unerased); drop(unerased);
} }
// Safety: requires layout of *e to match ErrorImpl<E>. // Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_ref<E>(e: &ErrorImpl<()>) -> &(dyn Diagnostic + Send + Sync + 'static) unsafe fn object_ref<E>(
e: Ref<'_, ErasedErrorImpl>,
) -> Ref<'_, dyn Diagnostic + Send + Sync + 'static>
where where
E: Diagnostic + Send + Sync + 'static, E: Diagnostic + Send + Sync + 'static,
{ {
// Attach E's native StdError vtable onto a pointer to self._object. // Attach E's native StdError vtable onto a pointer to self._object.
&(*(e as *const ErrorImpl<()> as *const ErrorImpl<E>))._object let unerased = e.cast::<ErrorImpl<E>>();
Ref::from_raw(NonNull::new_unchecked(
ptr::addr_of!((*unerased.as_ptr())._object) as *mut E,
))
} }
// Safety: requires layout of *e to match ErrorImpl<E>. // Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_mut<E>(e: &mut ErrorImpl<()>) -> &mut (dyn Diagnostic + Send + Sync + 'static) unsafe fn object_ref_stderr<E>(
where e: Ref<'_, ErasedErrorImpl>,
E: Diagnostic + Send + Sync + 'static, ) -> Ref<'_, dyn 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
}
// Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_ref_stderr<E>(e: &ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static)
where where
E: StdError + Send + Sync + 'static, E: StdError + Send + Sync + 'static,
{ {
// Attach E's native StdError vtable onto a pointer to self._object. // Attach E's native StdError vtable onto a pointer to self._object.
&(*(e as *const ErrorImpl<()> as *const ErrorImpl<E>))._object let unerased = e.cast::<ErrorImpl<E>>();
Ref::from_raw(NonNull::new_unchecked(
ptr::addr_of!((*unerased.as_ptr())._object) as *mut E,
))
} }
// Safety: requires layout of *e to match ErrorImpl<E>. // Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_boxed<E>(e: Box<ErrorImpl<()>>) -> Box<dyn Diagnostic + Send + Sync + 'static> unsafe fn object_boxed<E>(e: Own<ErasedErrorImpl>) -> Box<dyn Diagnostic + Send + Sync + 'static>
where where
E: Diagnostic + Send + Sync + 'static, E: Diagnostic + Send + Sync + 'static,
{ {
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below. // Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e) e.cast::<ErrorImpl<E>>().boxed()
} }
// Safety: requires layout of *e to match ErrorImpl<E>. // Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_boxed_stderr<E>(e: Box<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static> unsafe fn object_boxed_stderr<E>(
e: Own<ErasedErrorImpl>,
) -> Box<dyn StdError + Send + Sync + 'static>
where where
E: StdError + Send + Sync + 'static, E: StdError + Send + Sync + 'static,
{ {
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below. // Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e) e.cast::<ErrorImpl<E>>().boxed()
} }
// Safety: requires layout of *e to match ErrorImpl<E>. // Safety: requires layout of *e to match ErrorImpl<E>.
unsafe fn object_downcast<E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>> unsafe fn object_downcast<E>(e: Ref<'_, ErasedErrorImpl>, target: TypeId) -> Option<Ref<'_, ()>>
where where
E: 'static, E: 'static,
{ {
if TypeId::of::<E>() == target { if TypeId::of::<E>() == target {
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a // Caller is looking for an E pointer and e is ErrorImpl<E>, take a
// pointer to its E field. // pointer to its E field.
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<E>; let unerased = e.cast::<ErrorImpl<E>>();
let addr = &(*unerased)._object as *const E as *mut ();
Some(NonNull::new_unchecked(addr)) Some(
Ref::from_raw(NonNull::new_unchecked(
ptr::addr_of!((*unerased.as_ptr())._object) as *mut E,
))
.cast::<()>(),
)
} else { } else {
None None
} }
} }
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>. // Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>.
unsafe fn context_downcast<D, E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>> unsafe fn context_downcast<D, E>(e: Ref<'_, ErasedErrorImpl>, target: TypeId) -> Option<Ref<'_, ()>>
where where
D: 'static, D: 'static,
E: 'static, E: 'static,
{ {
if TypeId::of::<D>() == target { if TypeId::of::<D>() == target {
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>; let unerased = e.cast::<ErrorImpl<ContextError<D, E>>>().deref();
let addr = &(*unerased)._object.msg as *const D as *mut (); Some(Ref::new(&unerased._object.msg).cast::<()>())
Some(NonNull::new_unchecked(addr))
} else if TypeId::of::<E>() == target { } else if TypeId::of::<E>() == target {
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>; let unerased = e.cast::<ErrorImpl<ContextError<D, E>>>().deref();
let addr = &(*unerased)._object.error as *const E as *mut (); Some(Ref::new(&unerased._object.error).cast::<()>())
Some(NonNull::new_unchecked(addr))
} else { } else {
None None
} }
} }
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>. // Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>.
unsafe fn context_drop_rest<D, E>(e: Box<ErrorImpl<()>>, target: TypeId) unsafe fn context_drop_rest<D, E>(e: Own<ErasedErrorImpl>, target: TypeId)
where where
D: 'static, D: 'static,
E: 'static, E: 'static,
@ -573,61 +582,59 @@ where
// Called after downcasting by value to either the D or the E and doing a // Called after downcasting by value to either the D or the E and doing a
// ptr::read to take ownership of that value. // ptr::read to take ownership of that value.
if TypeId::of::<D>() == target { if TypeId::of::<D>() == target {
let unerased = mem::transmute::< let unerased = e
Box<ErrorImpl<()>>, .cast::<ErrorImpl<ContextError<ManuallyDrop<D>, E>>>()
Box<ErrorImpl<ContextError<ManuallyDrop<D>, E>>>, .boxed();
>(e);
drop(unerased); drop(unerased);
} else { } else {
let unerased = mem::transmute::< let unerased = e
Box<ErrorImpl<()>>, .cast::<ErrorImpl<ContextError<D, ManuallyDrop<E>>>>()
Box<ErrorImpl<ContextError<D, ManuallyDrop<E>>>>, .boxed();
>(e);
drop(unerased); drop(unerased);
} }
} }
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>. // Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
unsafe fn context_chain_downcast<D>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>> unsafe fn context_chain_downcast<D>(
e: Ref<'_, ErasedErrorImpl>,
target: TypeId,
) -> Option<Ref<'_, ()>>
where where
D: 'static, D: 'static,
{ {
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, Report>>; let unerased = e.cast::<ErrorImpl<ContextError<D, Report>>>().deref();
if TypeId::of::<D>() == target { if TypeId::of::<D>() == target {
let addr = &(*unerased)._object.msg as *const D as *mut (); Some(Ref::new(&unerased._object.msg).cast::<()>())
Some(NonNull::new_unchecked(addr))
} else { } else {
// Recurse down the context chain per the inner error's vtable. // Recurse down the context chain per the inner error's vtable.
let source = &(*unerased)._object.error; let source = &unerased._object.error;
(source.inner.vtable.object_downcast)(&source.inner, target) (vtable(source.inner.ptr).object_downcast)(source.inner.by_ref(), target)
} }
} }
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>. // Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
unsafe fn context_chain_drop_rest<D>(e: Box<ErrorImpl<()>>, target: TypeId) unsafe fn context_chain_drop_rest<D>(e: Own<ErasedErrorImpl>, target: TypeId)
where where
D: 'static, D: 'static,
{ {
// Called after downcasting by value to either the D or one of the causes // 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. // and doing a ptr::read to take ownership of that value.
if TypeId::of::<D>() == target { if TypeId::of::<D>() == target {
let unerased = mem::transmute::< let unerased = e
Box<ErrorImpl<()>>, .cast::<ErrorImpl<ContextError<ManuallyDrop<D>, Report>>>()
Box<ErrorImpl<ContextError<ManuallyDrop<D>, Report>>>, .boxed();
>(e);
// Drop the entire rest of the data structure rooted in the next Report. // Drop the entire rest of the data structure rooted in the next Report.
drop(unerased); drop(unerased);
} else { } else {
let unerased = mem::transmute::< let unerased = e
Box<ErrorImpl<()>>, .cast::<ErrorImpl<ContextError<D, ManuallyDrop<Report>>>>()
Box<ErrorImpl<ContextError<D, ManuallyDrop<Report>>>>, .boxed();
>(e); // Read out a ManuallyDrop<Box<ErasedErrorImpl>> from the next error.
// Read out a ManuallyDrop<Box<ErrorImpl<()>>> from the next error. let inner = unerased._object.error.inner;
let inner = ptr::read(&unerased._object.error.inner);
drop(unerased); drop(unerased);
let erased = ManuallyDrop::into_inner(inner); let vtable = vtable(inner.ptr);
// Recursively drop the next error using the same target typeid. // Recursively drop the next error using the same target typeid.
(erased.vtable.object_drop_rest)(erased, target); (vtable.object_drop_rest)(inner, target);
} }
} }
@ -649,36 +656,51 @@ pub(crate) struct ContextError<D, E> {
pub(crate) error: E, pub(crate) error: E,
} }
type ErasedErrorImpl = ErrorImpl<()>;
// Safety: `ErrorVTable` must be the first field of `ErrorImpl`
unsafe fn vtable(p: NonNull<ErasedErrorImpl>) -> &'static ErrorVTable {
(p.as_ptr() as *const &'static ErrorVTable).read()
}
impl<E> ErrorImpl<E> { impl<E> ErrorImpl<E> {
fn erase(&self) -> &ErrorImpl<()> { fn erase(&self) -> Ref<'_, ErasedErrorImpl> {
// Erase the concrete type of E but preserve the vtable in self.vtable // Erase the concrete type of E but preserve the vtable in self.vtable
// for manipulating the resulting thin pointer. This is analogous to an // for manipulating the resulting thin pointer. This is analogous to an
// unsize coercion. // unsize coercion.
unsafe { &*(self as *const ErrorImpl<E> as *const ErrorImpl<()>) } Ref::new(self).cast::<ErasedErrorImpl>()
} }
} }
impl ErrorImpl<()> { impl ErasedErrorImpl {
pub(crate) fn error(&self) -> &(dyn StdError + Send + Sync + 'static) { pub(crate) unsafe fn error<'a>(
this: Ref<'a, Self>,
) -> &'a (dyn StdError + Send + Sync + 'static) {
// Use vtable to attach E's native StdError vtable for the right // Use vtable to attach E's native StdError vtable for the right
// original type E. // original type E.
unsafe { &*(self.vtable.object_ref_stderr)(self) } (vtable(this.ptr).object_ref_stderr)(this).deref()
} }
pub(crate) fn diagnostic(&self) -> &(dyn Diagnostic + Send + Sync + 'static) { pub(crate) unsafe fn diagnostic<'a>(
this: Ref<'a, Self>,
) -> &'a (dyn Diagnostic + Send + Sync + 'static) {
// Use vtable to attach E's native StdError vtable for the right // Use vtable to attach E's native StdError vtable for the right
// original type E. // original type E.
unsafe { &*(self.vtable.object_ref)(self) } (vtable(this.ptr).object_ref)(this).deref()
} }
pub(crate) fn diagnostic_mut(&mut self) -> &mut (dyn Diagnostic + Send + Sync + 'static) { pub(crate) unsafe fn diagnostic_mut<'a>(
this: Mut<'a, Self>,
) -> &'a mut (dyn Diagnostic + Send + Sync + 'static) {
// Use vtable to attach E's native StdError vtable for the right // Use vtable to attach E's native StdError vtable for the right
// original type E. // original type E.
unsafe { &mut *(self.vtable.object_mut)(self) } (vtable(this.ptr).object_ref)(this.by_ref())
.by_mut()
.deref_mut()
} }
pub(crate) fn chain(&self) -> Chain<'_> { pub(crate) unsafe fn chain(this: Ref<'_, Self>) -> Chain<'_> {
Chain::new(self.error()) Chain::new(Self::error(this))
} }
} }
@ -687,7 +709,7 @@ where
E: StdError, E: StdError,
{ {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.erase().diagnostic().source() unsafe { ErrorImpl::diagnostic(self.erase()).source() }
} }
} }
@ -698,7 +720,7 @@ where
E: Debug, E: Debug,
{ {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.erase().debug(formatter) unsafe { ErrorImpl::debug(self.erase(), formatter) }
} }
} }
@ -707,7 +729,7 @@ where
E: Display, E: Display,
{ {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.erase().diagnostic(), formatter) unsafe { Display::fmt(ErrorImpl::diagnostic(self.erase()), formatter) }
} }
} }
@ -715,14 +737,9 @@ impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
fn from(error: Report) -> Self { fn from(error: Report) -> Self {
let outer = ManuallyDrop::new(error); let outer = ManuallyDrop::new(error);
unsafe { 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 // Use vtable to attach ErrorImpl<E>'s native StdError vtable for
// the right original type E. // the right original type E.
(erased.vtable.object_boxed)(erased) (vtable(outer.inner.ptr).object_boxed)(outer.inner)
} }
} }
} }
@ -731,14 +748,9 @@ impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
fn from(error: Report) -> Self { fn from(error: Report) -> Self {
let outer = ManuallyDrop::new(error); let outer = ManuallyDrop::new(error);
unsafe { 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 // Use vtable to attach ErrorImpl<E>'s native StdError vtable for
// the right original type E. // the right original type E.
(erased.vtable.object_boxed_stderr)(erased) (vtable(outer.inner.ptr).object_boxed_stderr)(outer.inner)
} }
} }
} }

View File

@ -1,18 +1,20 @@
use super::error::ErrorImpl; use super::{error::ErrorImpl, ptr::Ref};
use core::fmt; use core::fmt;
impl ErrorImpl<()> { impl ErrorImpl<()> {
pub(crate) fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub(crate) unsafe fn display(this: Ref<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.handler this.deref()
.handler
.as_ref() .as_ref()
.map(|handler| handler.display(self.error(), f)) .map(|handler| handler.display(Self::error(this), f))
.unwrap_or_else(|| core::fmt::Display::fmt(self.diagnostic(), f)) .unwrap_or_else(|| core::fmt::Display::fmt(Self::diagnostic(this), f))
} }
pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub(crate) unsafe fn debug(this: Ref<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.handler this.deref()
.handler
.as_ref() .as_ref()
.map(|handler| handler.debug(self.diagnostic(), f)) .map(|handler| handler.debug(Self::diagnostic(this), f))
.unwrap_or_else(|| core::fmt::Debug::fmt(self.diagnostic(), f)) .unwrap_or_else(|| core::fmt::Debug::fmt(Self::diagnostic(this), f))
} }
} }

View File

@ -5,7 +5,6 @@
clippy::wrong_self_convention clippy::wrong_self_convention
)] )]
use core::fmt::Display; use core::fmt::Display;
use core::mem::ManuallyDrop;
use std::error::Error as StdError; use std::error::Error as StdError;
@ -34,12 +33,15 @@ use crate::MietteHandler;
use error::ErrorImpl; use error::ErrorImpl;
use self::ptr::Own;
mod context; mod context;
mod error; mod error;
mod fmt; mod fmt;
mod into_diagnostic; mod into_diagnostic;
mod kind; mod kind;
mod macros; mod macros;
mod ptr;
mod wrapper; mod wrapper;
/** /**
@ -50,9 +52,12 @@ Core Diagnostic wrapper type.
You can just replace `use`s of `eyre::Report` with `miette::Report`. You can just replace `use`s of `eyre::Report` with `miette::Report`.
*/ */
pub struct Report { pub struct Report {
inner: ManuallyDrop<Box<ErrorImpl<()>>>, inner: Own<ErrorImpl<()>>,
} }
unsafe impl Sync for Report {}
unsafe impl Send for Report {}
/// ///
pub type ErrorHook = pub type ErrorHook =
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>; Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;

188
src/eyreish/ptr.rs Normal file
View File

@ -0,0 +1,188 @@
use std::{marker::PhantomData, ptr::NonNull};
#[repr(transparent)]
/// A raw pointer that owns its pointee
pub(crate) struct Own<T>
where
T: ?Sized,
{
pub(crate) ptr: NonNull<T>,
}
unsafe impl<T> Send for Own<T> where T: ?Sized {}
unsafe impl<T> Sync for Own<T> where T: ?Sized {}
impl<T> Copy for Own<T> where T: ?Sized {}
impl<T> Clone for Own<T>
where
T: ?Sized,
{
fn clone(&self) -> Self {
*self
}
}
impl<T> Own<T>
where
T: ?Sized,
{
pub(crate) fn new(ptr: Box<T>) -> Self {
Own {
ptr: unsafe { NonNull::new_unchecked(Box::into_raw(ptr)) },
}
}
pub(crate) fn cast<U: CastTo>(self) -> Own<U::Target> {
Own {
ptr: self.ptr.cast(),
}
}
pub(crate) unsafe fn boxed(self) -> Box<T> {
Box::from_raw(self.ptr.as_ptr())
}
pub(crate) fn by_ref<'a>(&self) -> Ref<'a, T> {
Ref {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub(crate) fn by_mut<'a>(self) -> Mut<'a, T> {
Mut {
ptr: self.ptr,
lifetime: PhantomData,
}
}
}
#[allow(explicit_outlives_requirements)]
#[repr(transparent)]
/// A raw pointer that represents a shared borrow of its pointee
pub(crate) struct Ref<'a, T>
where
T: ?Sized,
{
pub(crate) ptr: NonNull<T>,
lifetime: PhantomData<&'a T>,
}
impl<'a, T> Copy for Ref<'a, T> where T: ?Sized {}
impl<'a, T> Clone for Ref<'a, T>
where
T: ?Sized,
{
fn clone(&self) -> Self {
*self
}
}
impl<'a, T> Ref<'a, T>
where
T: ?Sized,
{
pub(crate) fn new(ptr: &'a T) -> Self {
Ref {
ptr: NonNull::from(ptr),
lifetime: PhantomData,
}
}
pub(crate) fn from_raw(ptr: NonNull<T>) -> Self {
Ref {
ptr,
lifetime: PhantomData,
}
}
pub(crate) fn cast<U: CastTo>(self) -> Ref<'a, U::Target> {
Ref {
ptr: self.ptr.cast(),
lifetime: PhantomData,
}
}
pub(crate) fn by_mut(self) -> Mut<'a, T> {
Mut {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub(crate) fn as_ptr(self) -> *const T {
self.ptr.as_ptr() as *const T
}
pub(crate) unsafe fn deref(self) -> &'a T {
&*self.ptr.as_ptr()
}
}
#[allow(explicit_outlives_requirements)]
#[repr(transparent)]
/// A raw pointer that represents a unique borrow of its pointee
pub(crate) struct Mut<'a, T>
where
T: ?Sized,
{
pub(crate) ptr: NonNull<T>,
lifetime: PhantomData<&'a mut T>,
}
impl<'a, T> Copy for Mut<'a, T> where T: ?Sized {}
impl<'a, T> Clone for Mut<'a, T>
where
T: ?Sized,
{
fn clone(&self) -> Self {
*self
}
}
impl<'a, T> Mut<'a, T>
where
T: ?Sized,
{
pub(crate) fn cast<U: CastTo>(self) -> Mut<'a, U::Target> {
Mut {
ptr: self.ptr.cast(),
lifetime: PhantomData,
}
}
pub(crate) fn by_ref(self) -> Ref<'a, T> {
Ref {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub(crate) fn extend<'b>(self) -> Mut<'b, T> {
Mut {
ptr: self.ptr,
lifetime: PhantomData,
}
}
pub(crate) unsafe fn deref_mut(self) -> &'a mut T {
&mut *self.ptr.as_ptr()
}
}
impl<'a, T> Mut<'a, T> {
pub(crate) unsafe fn read(self) -> T {
self.ptr.as_ptr().read()
}
}
pub(crate) trait CastTo {
type Target;
}
impl<T> CastTo for T {
type Target = T;
}

View File

@ -261,6 +261,7 @@ impl MietteHandlerOpts {
} }
} }
#[cfg(not(miri))]
pub(crate) fn get_width(&self) -> usize { pub(crate) fn get_width(&self) -> usize {
self.width.unwrap_or_else(|| { self.width.unwrap_or_else(|| {
terminal_size::terminal_size() terminal_size::terminal_size()
@ -269,6 +270,14 @@ impl MietteHandlerOpts {
.0 as usize .0 as usize
}) })
} }
#[cfg(miri)]
// miri doesn't support a syscall (specifically ioctl)
// performed by terminal_size, which causes test execution to fail
// so when miri is running we'll just fallback to a constant
pub(crate) fn get_width(&self) -> usize {
self.width.unwrap_or(80)
}
} }
/** /**

View File

@ -73,7 +73,7 @@ fn context_info<'a>(
} }
if offset >= (span.offset() + span.len()).saturating_sub(1) { if offset >= (span.offset() + span.len()).saturating_sub(1) {
let starting_offset = before_lines_starts.get(0).copied().unwrap_or_else(|| { let starting_offset = before_lines_starts.first().copied().unwrap_or_else(|| {
if context_lines_before == 0 { if context_lines_before == 0 {
span.offset() span.offset()
} else { } else {

View File

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