diff --git a/Cargo.toml b/Cargo.toml index 197d0dc..75d9a78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anyhow" -version = "1.0.97" +version = "1.0.98" authors = ["David Tolnay "] categories = ["rust-patterns", "no-std"] description = "Flexible concrete Error type built on std::error::Error" diff --git a/build.rs b/build.rs index 8124690..a0a9813 100644 --- a/build.rs +++ b/build.rs @@ -15,7 +15,7 @@ compile_error! { fn main() { let mut error_generic_member_access = false; if cfg!(feature = "std") { - println!("cargo:rerun-if-changed=build/probe.rs"); + println!("cargo:rerun-if-changed=src/nightly.rs"); let consider_rustc_bootstrap; if compile_probe(false) { @@ -68,6 +68,7 @@ fn main() { }; if rustc >= 80 { + println!("cargo:rustc-check-cfg=cfg(anyhow_build_probe)"); println!("cargo:rustc-check-cfg=cfg(anyhow_nightly_testing)"); println!("cargo:rustc-check-cfg=cfg(anyhow_no_core_error)"); println!("cargo:rustc-check-cfg=cfg(anyhow_no_core_unwind_safe)"); @@ -128,7 +129,7 @@ fn compile_probe(rustc_bootstrap: bool) -> bool { let rustc = cargo_env_var("RUSTC"); let out_dir = cargo_env_var("OUT_DIR"); let out_subdir = Path::new(&out_dir).join("probe"); - let probefile = Path::new("build").join("probe.rs"); + let probefile = Path::new("src").join("nightly.rs"); if let Err(err) = fs::create_dir(&out_subdir) { if err.kind() != ErrorKind::AlreadyExists { @@ -152,6 +153,7 @@ fn compile_probe(rustc_bootstrap: bool) -> bool { } cmd.stderr(Stdio::null()) + .arg("--cfg=anyhow_build_probe") .arg("--edition=2018") .arg("--crate-name=anyhow") .arg("--crate-type=lib") diff --git a/build/probe.rs b/build/probe.rs deleted file mode 100644 index 742d15c..0000000 --- a/build/probe.rs +++ /dev/null @@ -1,35 +0,0 @@ -// This code exercises the surface area that we expect of the Error generic -// member access API. If the current toolchain is able to compile it, then -// anyhow is able to provide backtrace support. - -#![feature(error_generic_member_access)] - -use core::error::{self, Error, Request}; -use core::fmt::{self, Debug, Display}; -use std::backtrace::Backtrace; - -struct MyError(Thing); -struct Thing; - -impl Debug for MyError { - fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { - unimplemented!() - } -} - -impl Display for MyError { - fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { - unimplemented!() - } -} - -impl Error for MyError { - fn provide<'a>(&'a self, request: &mut Request<'a>) { - request.provide_ref(&self.0); - } -} - -const _: fn(&dyn Error) -> Option<&Backtrace> = |err| error::request_ref::(err); - -// Include in sccache cache key. -const _: Option<&str> = option_env!("RUSTC_BOOTSTRAP"); diff --git a/src/backtrace.rs b/src/backtrace.rs index 303f911..cf35729 100644 --- a/src/backtrace.rs +++ b/src/backtrace.rs @@ -38,8 +38,7 @@ macro_rules! backtrace { #[cfg(error_generic_member_access)] macro_rules! backtrace_if_absent { ($err:expr) => { - match core::error::request_ref::($err as &dyn core::error::Error) - { + match $crate::nightly::request_ref_backtrace($err as &dyn core::error::Error) { Some(_) => None, None => backtrace!(), } diff --git a/src/context.rs b/src/context.rs index 326c2a4..52e6ab6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -4,7 +4,7 @@ use core::convert::Infallible; use core::fmt::{self, Debug, Display, Write}; #[cfg(error_generic_member_access)] -use core::error::Request; +use crate::nightly::{self, Request}; mod ext { use super::*; @@ -145,7 +145,7 @@ where #[cfg(error_generic_member_access)] fn provide<'a>(&'a self, request: &mut Request<'a>) { - StdError::provide(&self.error, request); + nightly::provide(&self.error, request); } } diff --git a/src/error.rs b/src/error.rs index 098b328..c4f0e2b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,13 @@ use crate::backtrace::Backtrace; use crate::chain::Chain; +#[cfg(error_generic_member_access)] +use crate::nightly::{self, Request}; #[cfg(any(feature = "std", not(anyhow_no_core_error), anyhow_no_ptr_addr_of))] use crate::ptr::Mut; use crate::ptr::{Own, Ref}; use crate::{Error, StdError}; use alloc::boxed::Box; use core::any::TypeId; -#[cfg(error_generic_member_access)] -use core::error::{self, Request}; use core::fmt::{self, Debug, Display}; use core::mem::ManuallyDrop; #[cfg(any(feature = "std", not(anyhow_no_core_error)))] @@ -157,7 +157,10 @@ impl Error { object_ref: object_ref::, #[cfg(anyhow_no_ptr_addr_of)] object_mut: object_mut::, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] object_boxed: object_boxed::, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + object_reallocate_boxed: object_reallocate_boxed::, object_downcast: object_downcast::, #[cfg(anyhow_no_ptr_addr_of)] object_downcast_mut: object_downcast_mut::, @@ -185,7 +188,10 @@ impl Error { object_ref: object_ref::>, #[cfg(all(any(feature = "std", not(anyhow_no_core_error)), anyhow_no_ptr_addr_of))] object_mut: object_mut::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] object_boxed: object_boxed::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + object_reallocate_boxed: object_reallocate_boxed::>, object_downcast: object_downcast::, #[cfg(anyhow_no_ptr_addr_of)] object_downcast_mut: object_downcast_mut::, @@ -214,7 +220,10 @@ impl Error { object_ref: object_ref::>, #[cfg(all(any(feature = "std", not(anyhow_no_core_error)), anyhow_no_ptr_addr_of))] object_mut: object_mut::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] object_boxed: object_boxed::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + object_reallocate_boxed: object_reallocate_boxed::>, object_downcast: object_downcast::, #[cfg(anyhow_no_ptr_addr_of)] object_downcast_mut: object_downcast_mut::, @@ -249,7 +258,10 @@ impl Error { object_ref: object_ref::>, #[cfg(anyhow_no_ptr_addr_of)] object_mut: object_mut::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] object_boxed: object_boxed::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + object_reallocate_boxed: object_reallocate_boxed::>, object_downcast: context_downcast::, #[cfg(anyhow_no_ptr_addr_of)] object_downcast_mut: context_downcast_mut::, @@ -278,7 +290,10 @@ impl Error { object_ref: object_ref::, #[cfg(anyhow_no_ptr_addr_of)] object_mut: object_mut::, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] object_boxed: object_boxed::, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + object_reallocate_boxed: object_reallocate_boxed::, object_downcast: object_downcast::>, #[cfg(anyhow_no_ptr_addr_of)] object_downcast_mut: object_downcast_mut::>, @@ -394,7 +409,10 @@ impl Error { object_ref: object_ref::>, #[cfg(all(any(feature = "std", not(anyhow_no_core_error)), anyhow_no_ptr_addr_of))] object_mut: object_mut::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] object_boxed: object_boxed::>, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + object_reallocate_boxed: object_reallocate_boxed::>, object_downcast: context_chain_downcast::, #[cfg(anyhow_no_ptr_addr_of)] object_downcast_mut: context_chain_downcast_mut::, @@ -603,6 +621,98 @@ impl Error { } } + /// Convert to a standard library error trait object. + /// + /// This is implemented as a cheap pointer cast that does not allocate or + /// deallocate memory. Like [`anyhow::Error::from_boxed`], it's useful for + /// interop with other error libraries. + /// + /// The same conversion is also available as + /// impl From<anyhow::Error> + /// for Box<dyn Error + Send + Sync + 'static>. + /// + /// If a backtrace was collected during construction of the `anyhow::Error`, + /// that backtrace remains accessible using the standard library `Error` + /// trait's provider API, but as a consequence, the resulting boxed error + /// can no longer be downcast to its original underlying type. + /// + /// ``` + #[cfg_attr(not(error_generic_member_access), doc = "# _ = stringify! {")] + /// #![feature(error_generic_member_access)] + /// + /// use anyhow::anyhow; + /// use std::backtrace::Backtrace; + /// use thiserror::Error; + /// + /// #[derive(Error, Debug)] + /// #[error("...")] + /// struct MyError; + /// + /// let anyhow_error = anyhow!(MyError); + /// println!("{}", anyhow_error.backtrace()); // has Backtrace + /// assert!(anyhow_error.downcast_ref::().is_some()); // can downcast + /// + /// let boxed_dyn_error = anyhow_error.into_boxed_dyn_error(); + /// assert!(std::error::request_ref::(&*boxed_dyn_error).is_some()); // has Backtrace + /// assert!(boxed_dyn_error.downcast_ref::().is_none()); // can no longer downcast + #[cfg_attr(not(error_generic_member_access), doc = "# };")] + /// ``` + /// + /// [`anyhow::Error::from_boxed`]: Self::from_boxed + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + #[must_use] + pub fn into_boxed_dyn_error(self) -> Box { + let outer = ManuallyDrop::new(self); + unsafe { + // Use vtable to attach ErrorImpl's native StdError vtable for + // the right original type E. + (vtable(outer.inner.ptr).object_boxed)(outer.inner) + } + } + + /// Convert to a standard library error trait object. + /// + /// Unlike `self.into_boxed_dyn_error()`, this method relocates the + /// underlying error into a new allocation in order to make it downcastable + /// to `&E` or `Box` for its original underlying error type. Any + /// backtrace collected during construction of the `anyhow::Error` is + /// discarded. + /// + /// ``` + #[cfg_attr(not(error_generic_member_access), doc = "# _ = stringify!{")] + /// #![feature(error_generic_member_access)] + /// + /// use anyhow::anyhow; + /// use std::backtrace::Backtrace; + /// use thiserror::Error; + /// + /// #[derive(Error, Debug)] + /// #[error("...")] + /// struct MyError; + /// + /// let anyhow_error = anyhow!(MyError); + /// println!("{}", anyhow_error.backtrace()); // has Backtrace + /// assert!(anyhow_error.downcast_ref::().is_some()); // can downcast + /// + /// let boxed_dyn_error = anyhow_error.reallocate_into_boxed_dyn_error_without_backtrace(); + /// assert!(std::error::request_ref::(&*boxed_dyn_error).is_none()); // Backtrace lost + /// assert!(boxed_dyn_error.downcast_ref::().is_some()); // can downcast to &MyError + /// assert!(boxed_dyn_error.downcast::().is_ok()); // can downcast to Box + #[cfg_attr(not(error_generic_member_access), doc = "# };")] + /// ``` + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + #[must_use] + pub fn reallocate_into_boxed_dyn_error_without_backtrace( + self, + ) -> Box { + let outer = ManuallyDrop::new(self); + unsafe { + // Use vtable to attach E's native StdError vtable for the right + // original type E. + (vtable(outer.inner.ptr).object_reallocate_boxed)(outer.inner) + } + } + #[cfg(error_generic_member_access)] pub(crate) fn provide<'a>(&'a self, request: &mut Request<'a>) { unsafe { ErrorImpl::provide(self.inner.by_ref(), request) } @@ -674,7 +784,10 @@ struct ErrorVTable { object_ref: unsafe fn(Ref) -> Ref, #[cfg(all(any(feature = "std", not(anyhow_no_core_error)), anyhow_no_ptr_addr_of))] object_mut: unsafe fn(Mut) -> &mut (dyn StdError + Send + Sync + 'static), + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] object_boxed: unsafe fn(Own) -> Box, + #[cfg(any(feature = "std", not(anyhow_no_core_error)))] + object_reallocate_boxed: unsafe fn(Own) -> Box, object_downcast: unsafe fn(Ref, TypeId) -> Option>, #[cfg(anyhow_no_ptr_addr_of)] object_downcast_mut: unsafe fn(Mut, TypeId) -> Option>, @@ -735,6 +848,7 @@ where } // Safety: requires layout of *e to match ErrorImpl. +#[cfg(any(feature = "std", not(anyhow_no_core_error)))] unsafe fn object_boxed(e: Own) -> Box where E: StdError + Send + Sync + 'static, @@ -744,6 +858,17 @@ where unsafe { unerased_own.boxed() } } +// Safety: requires layout of *e to match ErrorImpl. +#[cfg(any(feature = "std", not(anyhow_no_core_error)))] +unsafe fn object_reallocate_boxed(e: Own) -> Box +where + E: StdError + Send + Sync + 'static, +{ + // Attach E's native StdError vtable. + let unerased_own = e.cast::>(); + Box::new(unsafe { unerased_own.boxed() }._object) +} + // Safety: requires layout of *e to match ErrorImpl. unsafe fn object_downcast(e: Ref, target: TypeId) -> Option> where @@ -995,7 +1120,7 @@ impl ErrorImpl { .as_ref() .or_else(|| { #[cfg(error_generic_member_access)] - return error::request_ref::(unsafe { Self::error(this) }); + return nightly::request_ref_backtrace(unsafe { Self::error(this) }); #[cfg(not(error_generic_member_access))] return unsafe { (vtable(this.ptr).object_backtrace)(this) }; }) @@ -1005,9 +1130,9 @@ impl ErrorImpl { #[cfg(error_generic_member_access)] unsafe fn provide<'a>(this: Ref<'a, Self>, request: &mut Request<'a>) { if let Some(backtrace) = unsafe { &this.deref().backtrace } { - request.provide_ref(backtrace); + nightly::provide_ref_backtrace(request, backtrace); } - unsafe { Self::error(this) }.provide(request); + nightly::provide(unsafe { Self::error(this) }, request); } #[cold] @@ -1048,27 +1173,27 @@ where } } +#[cfg(any(feature = "std", not(anyhow_no_core_error)))] impl From for Box { #[cold] fn from(error: Error) -> Self { - let outer = ManuallyDrop::new(error); - unsafe { - // Use vtable to attach ErrorImpl's native StdError vtable for - // the right original type E. - (vtable(outer.inner.ptr).object_boxed)(outer.inner) - } + error.into_boxed_dyn_error() } } +#[cfg(any(feature = "std", not(anyhow_no_core_error)))] impl From for Box { + #[cold] fn from(error: Error) -> Self { - Box::::from(error) + error.into_boxed_dyn_error() } } +#[cfg(any(feature = "std", not(anyhow_no_core_error)))] impl From for Box { + #[cold] fn from(error: Error) -> Self { - Box::::from(error) + error.into_boxed_dyn_error() } } diff --git a/src/lib.rs b/src/lib.rs index 91f76fe..e9debbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -206,7 +206,7 @@ //! function that returns Anyhow's error type, as the trait that `?`-based error //! conversions are defined by is only available in std in those old versions. -#![doc(html_root_url = "https://docs.rs/anyhow/1.0.97")] +#![doc(html_root_url = "https://docs.rs/anyhow/1.0.98")] #![cfg_attr(error_generic_member_access, feature(error_generic_member_access))] #![no_std] #![deny(dead_code, unused_imports, unused_mut)] @@ -260,6 +260,8 @@ mod error; mod fmt; mod kind; mod macros; +#[cfg(error_generic_member_access)] +mod nightly; mod ptr; mod wrapper; diff --git a/src/nightly.rs b/src/nightly.rs new file mode 100644 index 0000000..125fe36 --- /dev/null +++ b/src/nightly.rs @@ -0,0 +1,58 @@ +// This code exercises the surface area that we expect of the Error generic +// member access API. If the current toolchain is able to compile it, then +// anyhow is able to provide backtrace support. + +#![cfg_attr(anyhow_build_probe, feature(error_generic_member_access))] + +use core::error::{self, Error}; +use std::backtrace::Backtrace; + +pub use core::error::Request; + +#[cfg(anyhow_build_probe)] +const _: () = { + use core::fmt::{self, Debug, Display}; + + struct MyError(Backtrace); + + impl Debug for MyError { + fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { + unimplemented!() + } + } + + impl Display for MyError { + fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { + unimplemented!() + } + } + + impl Error for MyError { + fn provide<'a>(&'a self, request: &mut Request<'a>) { + provide_ref_backtrace(request, &self.0); + } + } +}; + +// Include in sccache cache key. +#[cfg(anyhow_build_probe)] +const _: Option<&str> = option_env!("RUSTC_BOOTSTRAP"); + +pub fn request_ref_backtrace(err: &dyn Error) -> Option<&Backtrace> { + request_ref::(err) +} + +fn request_ref<'a, T>(err: &'a (impl Error + ?Sized)) -> Option<&'a T> +where + T: 'static + ?Sized, +{ + error::request_ref::(err) +} + +pub fn provide_ref_backtrace<'a>(request: &mut Request<'a>, backtrace: &'a Backtrace) { + Request::provide_ref(request, backtrace); +} + +pub fn provide<'a>(err: &'a (impl Error + ?Sized), request: &mut Request<'a>) { + Error::provide(err, request); +} diff --git a/src/wrapper.rs b/src/wrapper.rs index 6f46779..bad3ddd 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -5,7 +5,7 @@ use core::fmt::{self, Debug, Display}; use alloc::boxed::Box; #[cfg(error_generic_member_access)] -use core::error::Request; +use crate::nightly::{self, Request}; #[repr(transparent)] pub struct MessageError(pub M); @@ -79,6 +79,6 @@ impl StdError for BoxedError { #[cfg(error_generic_member_access)] fn provide<'a>(&'a self, request: &mut Request<'a>) { - self.0.provide(request); + nightly::provide(&*self.0, request); } }