-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Fix using downcast with anyhow::Error
#12689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,10 @@ pub(crate) unsafe trait ErrorExt: Send + Sync + 'static { | |
| /// Take the backtrace from this error, if any. | ||
| #[cfg(feature = "backtrace")] | ||
| fn take_backtrace(&mut self) -> Option<Backtrace>; | ||
|
|
||
| /// Conversion into an `anyhow::Error`. | ||
| #[cfg(feature = "anyhow")] | ||
| fn ext_into_anyhow(self) -> anyhow::Error; | ||
| } | ||
|
|
||
| /// Morally a `dyn ErrorExt` trait object that holds its own vtable. | ||
|
|
@@ -542,7 +546,7 @@ impl From<Error> for Box<dyn core::error::Error + 'static> { | |
| impl From<Error> for anyhow::Error { | ||
| #[inline] | ||
| fn from(e: Error) -> Self { | ||
| anyhow::Error::from_boxed(e.into_boxed_dyn_error()) | ||
| e.inner.into_anyhow() | ||
|
Comment on lines
-545
to
+549
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make sure I understand, it isn't enough to change this e.downcast::<anyhow::Error>().unwrap_or_else(|e| {
anyhow::Error::from_boxed(e.into_boxed_dyn_error())
})and expose the underlying
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The specific case that I ran into updating Spin was that a So I believe what you've gisted will solve anyhow-and-back but wouldn't solve acquiring Wasmtime's error types which never went through |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -1241,6 +1245,11 @@ where | |
| fn take_backtrace(&mut self) -> Option<Backtrace> { | ||
| None | ||
| } | ||
|
|
||
| #[cfg(feature = "anyhow")] | ||
| fn ext_into_anyhow(self) -> anyhow::Error { | ||
| anyhow::Error::new(self.0) | ||
| } | ||
| } | ||
|
|
||
| /// `ErrorExt` wrapper for types given to `Error::msg`. | ||
|
|
@@ -1319,6 +1328,11 @@ where | |
| fn take_backtrace(&mut self) -> Option<Backtrace> { | ||
| None | ||
| } | ||
|
|
||
| #[cfg(feature = "anyhow")] | ||
| fn ext_into_anyhow(self) -> anyhow::Error { | ||
| anyhow::Error::msg(self.0) | ||
| } | ||
| } | ||
|
|
||
| /// `ErrorExt` wrapper for `Box<dyn core::error::Error>`. | ||
|
|
@@ -1374,6 +1388,11 @@ unsafe impl ErrorExt for BoxedError { | |
| fn take_backtrace(&mut self) -> Option<Backtrace> { | ||
| None | ||
| } | ||
|
|
||
| #[cfg(feature = "anyhow")] | ||
| fn ext_into_anyhow(self) -> anyhow::Error { | ||
| anyhow::Error::from_boxed(self.0) | ||
| } | ||
| } | ||
|
|
||
| /// `ErrorExt` wrapper for `anyhow::Error`. | ||
|
|
@@ -1430,6 +1449,11 @@ unsafe impl ErrorExt for AnyhowError { | |
| fn take_backtrace(&mut self) -> Option<Backtrace> { | ||
| None | ||
| } | ||
|
|
||
| #[cfg(feature = "anyhow")] | ||
| fn ext_into_anyhow(self) -> anyhow::Error { | ||
| self.0 | ||
| } | ||
| } | ||
|
|
||
| pub(crate) enum OomOrDynErrorRef<'a> { | ||
|
|
@@ -1754,6 +1778,23 @@ impl OomOrDynError { | |
| } | ||
| } | ||
|
|
||
| #[cfg(feature = "anyhow")] | ||
| pub(crate) fn into_anyhow(self) -> anyhow::Error { | ||
| if self.is_oom() { | ||
| // Safety: `self.is_oom()` is true. | ||
| anyhow::Error::from(unsafe { *self.unchecked_oom() }) | ||
| } else { | ||
| debug_assert!(self.is_boxed_dyn_error()); | ||
| // Safety: this is a boxed dyn error. | ||
| let ptr = unsafe { self.unchecked_into_dyn_error() }; | ||
| // Safety: invariant of the type that the pointer is valid. | ||
| let vtable = unsafe { ptr.as_ref().vtable }; | ||
| // Safety: the pointer is valid and the vtable is associated with | ||
| // this pointer's concrete error type. | ||
| unsafe { (vtable.into_anyhow)(ptr) } | ||
| } | ||
| } | ||
|
|
||
| /// Given that this is known to be an instance of the type associated with | ||
| /// the given `TypeId`, do an owning-downcast to that type, writing the | ||
| /// result through the given `ret_ptr`, and deallocating `self` along the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -905,3 +905,40 @@ fn oom_requested_allocation_size() { | |
| let oom = OutOfMemory::new(usize::MAX); | ||
| assert!(oom.requested_allocation_size() <= usize::try_from(isize::MAX).unwrap()); | ||
| } | ||
|
|
||
| #[test] | ||
| #[cfg(feature = "anyhow")] | ||
| fn anyhow_preserves_downcast() -> Result<()> { | ||
| { | ||
| let e: Error = TestError(1).into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| let e: anyhow::Error = e.into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| } | ||
| { | ||
| let e = Error::from(TestError(1)).context("hi"); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| assert!(e.downcast_ref::<&str>().is_some()); | ||
| let e: anyhow::Error = e.into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| assert!(e.downcast_ref::<&str>().is_some()); | ||
| } | ||
| Ok(()) | ||
| } | ||
|
Comment on lines
+911
to
+927
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes me realize let e = anyhow::Error::from(TestError(1));
let e = e.context("str");
let e = wasmtime::Error::from(e);
assert!(e.is::<anyhow::Error>()); // should pass right now
assert!(e.is::<TestError>()); // I think will fail
assert!(e.is::<&str>()); // I think will also failAnd in fact, I don't think we can support this, given that our vtable methods can't be generic over I guess we could at the impl wasmtime::Error {
pub fn is<E>(&self) -> bool {
let result = existing_is_impl();
#[cfg(feature = "anyhow")]
let result = result || self.downcast_ref::<anyhow::Error>().is_some_and(|e| {
e.is::<E>()
});
result
}
}?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good point, and yeah I think hooking
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fine to do as a follow up too, ofc, if you prefer
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm ok yeah this is going to be a little gnarly, so I've added a test for the current behavior documenting that it's a bit buggy, but it at least preserves the |
||
|
|
||
| #[test] | ||
| #[cfg(feature = "anyhow")] | ||
| fn anyhow_source_loses_downcast() -> Result<()> { | ||
| let e: anyhow::Error = TestError(1).into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
|
|
||
| let e: Error = Error::from_anyhow(e); | ||
| // FIXME: this should actually test for `is_some` | ||
| assert!(e.downcast_ref::<TestError>().is_none()); | ||
|
|
||
| // Even while the above is broken, when going back to `anyhow` we should | ||
| // preserve the original error. | ||
| let e: anyhow::Error = e.into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| Ok(()) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I guess
anyhow'sis/downcastcould never see through our context chains when all they have is aBox<dyn core::error::Error>. Okay, I get it.