diff --git a/src/error.rs b/src/error.rs index f8871e0..afe006c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,10 +27,17 @@ impl Error { where E: StdError + Send + Sync + 'static, { - let type_id = TypeId::of::(); + let vtable = &ErrorVTable { + object_drop: object_drop::, + object_drop_front: object_drop_front::, + object_ref: object_ref::, + object_mut: object_mut::, + object_boxed: object_boxed::, + object_is: object_is::, + }; - // Safety: passing typeid of the right type E. - unsafe { Error::construct(error, type_id, backtrace) } + // Safety: passing vtable that operates on the right type E. + unsafe { Error::construct(error, vtable, backtrace) } } pub(crate) fn from_adhoc(message: M, backtrace: Option) -> Self @@ -38,11 +45,18 @@ impl Error { M: Display + Debug + Send + Sync + 'static, { let error = MessageError(message); - let type_id = TypeId::of::(); + let vtable = &ErrorVTable { + object_drop: object_drop::>, + object_drop_front: object_drop_front::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_is: object_is::, + }; - // Safety: MessageError is repr(transparent) so MessageError has the - // same layout as the typeid specifies. - unsafe { Error::construct(error, type_id, backtrace) } + // Safety: MessageError is repr(transparent) so it is okay for the + // vtable to allow casting the MessageError to M. + unsafe { Error::construct(error, vtable, backtrace) } } pub(crate) fn from_display(message: M, backtrace: Option) -> Self @@ -50,11 +64,18 @@ impl Error { M: Display + Send + Sync + 'static, { let error = DisplayError(message); - let type_id = TypeId::of::(); + let vtable = &ErrorVTable { + object_drop: object_drop::>, + object_drop_front: object_drop_front::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_is: object_is::, + }; - // Safety: DisplayError is repr(transparent) so DisplayError has the - // same layout as the typeid specifies. - unsafe { Error::construct(error, type_id, backtrace) } + // Safety: DisplayError is repr(transparent) so it is okay for the + // vtable to allow casting the DisplayError to M. + unsafe { Error::construct(error, vtable, backtrace) } } pub(crate) fn from_context(context: C, error: E) -> Self @@ -68,22 +89,18 @@ impl Error { // Takes backtrace as argument rather than capturing it here so that the // user sees one fewer layer of wrapping noise in the backtrace. // - // Unsafe because the type represented by type_id must have the same layout - // as E or else we allow invalid downcasts. - unsafe fn construct(error: E, type_id: TypeId, backtrace: Option) -> Self + // Unsafe because the given vtable must have sensible behavior on the error + // value of type E. + unsafe fn construct( + error: E, + vtable: &'static ErrorVTable, + backtrace: Option, + ) -> Self where E: StdError + Send + Sync + 'static, { - let vtable = &ErrorVTable { - object_drop: object_drop::, - object_drop_front: object_drop_front::, - object_ref: object_ref::, - object_mut: object_mut::, - object_boxed: object_boxed::, - }; let inner = Box::new(ErrorImpl { vtable, - type_id, backtrace, _error: error, }); @@ -216,7 +233,8 @@ impl Error { where E: Display + Debug + Send + Sync + 'static, { - TypeId::of::() == self.inner.type_id + let target = TypeId::of::(); + unsafe { (self.inner.vtable.object_is)(target) } } /// Attempt to downcast the error object to a concrete type. @@ -359,6 +377,7 @@ struct ErrorVTable { object_ref: unsafe fn(&ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static), object_mut: unsafe fn(&mut ErrorImpl<()>) -> &mut (dyn StdError + Send + Sync + 'static), object_boxed: unsafe fn(Box>) -> Box, + object_is: unsafe fn(TypeId) -> bool, } unsafe fn object_drop(e: Box>) { @@ -397,11 +416,17 @@ where mem::transmute::>, Box>>(e) } +unsafe fn object_is(target: TypeId) -> bool +where + T: 'static, +{ + TypeId::of::() == target +} + // repr C to ensure that `E` remains in the final position #[repr(C)] pub(crate) struct ErrorImpl { vtable: &'static ErrorVTable, - type_id: TypeId, backtrace: Option, // NOTE: Don't use directly. Use only through vtable. Erased type may have different alignment. _error: E,