From ee2077e01d2cd1a1680cc0042997620cb6ddb917 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 12 Aug 2023 04:07:49 +0200 Subject: [PATCH 1/4] lib: fix typos (#88) * lib: fix typos * lib: more typos --- src/lib.rs | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e7251c2..68c2649 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,16 +2,16 @@ //! error handling type for easy idiomatic error handling and reporting in Rust //! applications. //! -//! This crate is a fork of [`anyhow`] with a support for customized -//! error reports. For more details on customization checkout the docs on +//! This crate is a fork of [`anyhow`] with support for customized +//! error reports. For more details on customization, check out the docs on //! [`eyre::EyreHandler`]. //! //! ## Custom Report Handlers //! -//! The heart of this crate is it's ability to swap out the Handler type to change +//! The heart of this crate is its ability to swap out the Handler type to change //! what information is carried alongside errors and how the end report is //! formatted. This crate is meant to be used alongside companion crates that -//! customize it's behavior. Below is a list of known crates that export report +//! customize its behavior. Below is a list of known crates that export report //! handlers for eyre and short summaries of what features they provide. //! //! - [`stable-eyre`]: Switches the backtrace type from `std`'s to `backtrace-rs`'s @@ -43,11 +43,11 @@ //! errors while not preventing them from depending on details you didn't mean //! to make part of your public API. //! - This in turn makes the error types of all libraries you use a part of -//! your public API as well, and makes changing any of those libraries into an +//! your public API as well, and makes changing any of those libraries into //! undetectable runtime breakage. -//! - If many of your errors are constructed from strings you encourage your -//! users to use string comparision for reacting to specific errors which is -//! brittle and turns updating error messages into a potentially undetectable +//! - If many of your errors are constructed from strings, you encourage your +//! users to use string comparison for reacting to specific errors, which is +//! brittle and turns updating error messages into potentially undetectable //! runtime breakage. //! //! ## Details @@ -86,7 +86,7 @@ //! ``` //! //! - Wrap a lower level error with a new error created from a message to help the -//! person troubleshooting understand what the chain of failures that occured. A +//! person troubleshooting understand the chain of failures that occurred. A //! low-level error like "No such file or directory" can be annoying to debug //! without more information about what higher level step the application was in //! the middle of. @@ -132,7 +132,7 @@ //! No such file or directory (os error 2) //! ``` //! -//! - Downcasting is supported and can be by value, by shared reference, or by +//! - Downcasting is supported and can be done by value, by shared reference, or by //! mutable reference as needed. //! //! ```rust @@ -219,7 +219,8 @@ //! # Ok(()) //! # } //! ``` -//! - On newer versions of the compiler (e.g. 1.58 and later) this macro also +//! +//! - On newer versions of the compiler (i.e. 1.58 and later) this macro also //! supports format args captures. //! //! ```rust @@ -239,7 +240,7 @@ //! everything works still. I'm waiting for upstream fixes to be merged rather than //! fixing them myself, so bear with me. //! -//! In no_std mode, the same API is almost all available and works the same way. To +//! In no_std mode, almost all the API is available and works the same way. To //! depend on Eyre in no_std mode, disable our default enabled "std" feature in //! Cargo.toml. A global allocator is required. //! @@ -275,7 +276,7 @@ //! ## Compatibility with `anyhow` //! //! This crate does its best to be usable as a drop in replacement of `anyhow` and -//! vice-versa by `re-exporting` all of the renamed APIs with the names used in +//! vice-versa by re-exporting all of the renamed APIs with the names used in //! `anyhow`, though there are some differences still. //! //! #### `Context` and `Option` @@ -379,7 +380,7 @@ use core::mem::ManuallyDrop; use std::error::Error as StdError; pub use eyre as format_err; -/// Compatibility re-export of `eyre` for interopt with `anyhow` +/// Compatibility re-export of `eyre` for interop with `anyhow` pub use eyre as anyhow; use once_cell::sync::OnceCell; #[doc(hidden)] @@ -388,9 +389,9 @@ pub use DefaultHandler as DefaultContext; pub use EyreHandler as EyreContext; #[doc(hidden)] pub use Report as ErrReport; -/// Compatibility re-export of `Report` for interopt with `anyhow` +/// Compatibility re-export of `Report` for interop with `anyhow` pub use Report as Error; -/// Compatibility re-export of `WrapErr` for interopt with `anyhow` +/// Compatibility re-export of `WrapErr` for interop with `anyhow` pub use WrapErr as Context; /// The core error reporting type of the library, a wrapper around a dynamic error reporting type. @@ -1108,13 +1109,13 @@ pub trait WrapErr: context::private::Sealed { D: Display + Send + Sync + 'static, F: FnOnce() -> D; - /// Compatibility re-export of wrap_err for interopt with `anyhow` + /// Compatibility re-export of wrap_err for interop with `anyhow` #[cfg_attr(track_caller, track_caller)] fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static; - /// Compatibility re-export of wrap_err_with for interopt with `anyhow` + /// Compatibility re-export of wrap_err_with for interop with `anyhow` #[cfg_attr(track_caller, track_caller)] fn with_context(self, f: F) -> Result where From 2d984da8457f04e8f4269aa450bf2a096e53e8d6 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Wed, 16 Aug 2023 20:52:32 +0200 Subject: [PATCH 2/4] Cleanup internals (#101) * fix: const_err is not a hard error See: https://github.com/rust-lang/rust/issues/71800 * fix: lints * fix: macro doctests * fix: renamed lint * fix: restore use of enum variant --- Cargo.toml | 2 +- src/lib.rs | 4 ++-- tests/test_context.rs | 6 +++--- tests/test_macros.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 11556f2..ddddab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ thiserror = "1.0" trybuild = { version = "1.0.19", features = ["diff"] } backtrace = "0.3.46" anyhow = "1.0.28" -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0", features = ["full"] } pyo3 = { version = "0.13", default-features = false, features = ["auto-initialize"] } [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 68c2649..37885b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,11 +332,11 @@ #![warn( missing_debug_implementations, missing_docs, - missing_doc_code_examples, + // FIXME: this lint is currently nightly only + rustdoc::missing_doc_code_examples, rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, diff --git a/tests/test_context.rs b/tests/test_context.rs index df33536..df536c5 100644 --- a/tests/test_context.rs +++ b/tests/test_context.rs @@ -21,7 +21,7 @@ macro_rules! context_type { #[derive(Debug)] struct $name { message: &'static str, - drop: DetectDrop, + _drop: DetectDrop, } impl Display for $name { @@ -74,7 +74,7 @@ fn make_chain() -> (Report, Dropped) { let mid = Err::<(), LowLevel>(low) .wrap_err(MidLevel { message: "failed to load config", - drop: DetectDrop::new(&dropped.mid), + _drop: DetectDrop::new(&dropped.mid), }) .unwrap_err(); @@ -82,7 +82,7 @@ fn make_chain() -> (Report, Dropped) { let high = Err::<(), Report>(mid) .wrap_err(HighLevel { message: "failed to start server", - drop: DetectDrop::new(&dropped.high), + _drop: DetectDrop::new(&dropped.high), }) .unwrap_err(); diff --git a/tests/test_macros.rs b/tests/test_macros.rs index abcc3c9..2bcad38 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -62,7 +62,7 @@ fn test_temporaries() { // time it's done evaluating, those will stick around until the // semicolon, which is on the other side of the await point, making the // enclosing future non-Send. - Ready(Some(eyre!("..."))).await; + let _ = Ready(Some(eyre!("..."))).await; }); fn message(cell: Cell<&str>) -> &str { @@ -70,7 +70,7 @@ fn test_temporaries() { } require_send_sync(async { - Ready(Some(eyre!(message(Cell::new("..."))))).await; + let _ = Ready(Some(eyre!(message(Cell::new("..."))))).await; }); } From 9f4ecc497ed544122c19fc158a033abd0edc2aab Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Tue, 19 Sep 2023 22:02:20 +0200 Subject: [PATCH 3/4] 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` 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 --- .github/workflows/ci.yml | 19 +- Cargo.toml | 2 +- src/context.rs | 4 +- src/error.rs | 389 ++++++++++++++++++++++++++------------- src/fmt.rs | 21 ++- src/lib.rs | 6 +- src/ptr.rs | 149 +++++++++++++++ src/wrapper.rs | 3 + tests/compiletest.rs | 1 + tests/test_location.rs | 22 ++- 10 files changed, 469 insertions(+), 147 deletions(-) create mode 100644 src/ptr.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77b8743..1034a0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index ddddab5..8cdfcc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/src/context.rs b/src/context.rs index fef121f..a15b41b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -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())) } } diff --git a/src/error.rs b/src/error.rs index d6a2433..701a0fd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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(error: E) -> Self where E: StdError + Send + Sync + 'static, @@ -80,6 +82,7 @@ impl Report { object_mut: object_mut::, object_boxed: object_boxed::, object_downcast: object_downcast::, + object_downcast_mut: object_downcast_mut::, object_drop_rest: object_drop_front::, }; @@ -102,6 +105,7 @@ impl Report { object_mut: object_mut::>, object_boxed: object_boxed::>, object_downcast: object_downcast::, + object_downcast_mut: object_downcast_mut::, object_drop_rest: object_drop_front::, }; @@ -125,6 +129,7 @@ impl Report { object_mut: object_mut::>, object_boxed: object_boxed::>, object_downcast: object_downcast::, + object_downcast_mut: object_downcast_mut::, object_drop_rest: object_drop_front::, }; @@ -149,6 +154,7 @@ impl Report { object_mut: object_mut::>, object_boxed: object_boxed::>, object_downcast: context_downcast::, + object_downcast_mut: context_downcast_mut::, object_drop_rest: context_drop_rest::, }; @@ -170,6 +176,7 @@ impl Report { object_mut: object_mut::, object_boxed: object_boxed::, object_downcast: object_downcast::>, + object_downcast_mut: object_downcast_mut::>, object_drop_rest: object_drop_front::>, }; @@ -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> to - // Box> except that the - // result is a thin pointer. The necessary behavior for manipulating the - // underlying ErrorImpl is preserved in the vtable provided by the - // caller rather than a builtin fat pointer vtable. - let erased = mem::transmute::>, Box>>(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::>::new(inner); + + // Safety: the type + let ptr = ptr.cast::>(); + 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` 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 = ContextError { msg, error: self }; let vtable = &ErrorVTable { @@ -273,6 +284,7 @@ impl Report { object_mut: object_mut::>, object_boxed: object_boxed::>, object_downcast: context_chain_downcast::, + object_downcast_mut: context_chain_downcast_mut::, object_drop_rest: context_chain_drop_rest::, }; @@ -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 — 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> 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::().as_ptr()) + let addr = (self.vtable().object_downcast)(self.inner.as_ref(), target)?; + Some(addr.cast::().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::().as_ptr()) + let addr = (self.vtable().object_downcast_mut)(self.inner.as_mut(), target)?; + Some(addr.cast::().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> 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>), - 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>), + 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>) -> Box, - object_downcast: unsafe fn(&ErrorImpl<()>, TypeId) -> Option>, - object_drop_rest: unsafe fn(Box>, TypeId), + object_boxed: unsafe fn(OwnedPtr>) -> Box, + object_downcast: unsafe fn(RefPtr<'_, ErrorImpl<()>>, TypeId) -> Option>, + object_downcast_mut: unsafe fn(MutPtr<'_, ErrorImpl<()>>, TypeId) -> Option>, + object_drop_rest: unsafe fn(OwnedPtr>, TypeId), } -// Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_drop(e: Box>) { - // Cast back to ErrorImpl 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> = Box::from_raw(Box::into_raw(e).cast()); +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +unsafe fn object_drop(e: OwnedPtr>) { + // Cast to a context type and drop the Box allocation. + let unerased = unsafe { e.cast::>().into_box() }; drop(unerased); } -// Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_drop_front(e: Box>, target: TypeId) { +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +unsafe fn object_drop_front(e: OwnedPtr>, 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: Box>, 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>> = Box::from_raw(Box::into_raw(e).cast()); - drop(unerased); + let unerased = unsafe { e.cast::>().into_box() }; + + mem::forget(unerased._object) } -// Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_ref(e: &ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static) +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +unsafe fn object_ref(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))._object + &unsafe { e.cast::>().as_ref() }._object } -// Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_mut(e: &mut ErrorImpl<()>) -> &mut (dyn StdError + Send + Sync + 'static) +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +unsafe fn object_mut(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))._object + &mut unsafe { e.cast::>().into_mut() }._object } -// Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_boxed(e: Box>) -> Box +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +unsafe fn object_boxed(e: OwnedPtr>) -> Box where E: StdError + Send + Sync + 'static, { // Attach ErrorImpl's native StdError vtable. The StdError impl is below. - mem::transmute::>, Box>>(e) + unsafe { e.cast::>().into_box() } +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +unsafe fn object_downcast(e: RefPtr<'_, ErrorImpl<()>>, target: TypeId) -> Option> +where + E: 'static, +{ + if TypeId::of::() == target { + // Caller is looking for an E pointer and e is ErrorImpl, take a + // pointer to its E field. + let unerased = unsafe { e.cast::>().as_ref() }; + Some(NonNull::from(&(unerased._object)).cast::<()>()) + } else { + None + } } -// Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_downcast(e: &ErrorImpl<()>, target: TypeId) -> Option> +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl. +unsafe fn object_downcast_mut( + e: MutPtr<'_, ErrorImpl<()>>, + target: TypeId, +) -> Option> where E: 'static, { if TypeId::of::() == target { // Caller is looking for an E pointer and e is ErrorImpl, take a // pointer to its E field. - let unerased = e as *const ErrorImpl<()> as *const ErrorImpl; - let addr = &(*unerased)._object as *const E as *mut (); - Some(NonNull::new_unchecked(addr)) + let unerased = unsafe { e.cast::>().into_mut() }; + Some(NonNull::from(&mut (unerased._object)).cast::<()>()) } else { None } } -// Safety: requires layout of *e to match ErrorImpl>. -unsafe fn context_downcast(e: &ErrorImpl<()>, target: TypeId) -> Option> +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl>. +unsafe fn context_downcast( + e: RefPtr<'_, ErrorImpl<()>>, + target: TypeId, +) -> Option> where D: 'static, E: 'static, { if TypeId::of::() == target { - let unerased = e as *const ErrorImpl<()> as *const ErrorImpl>; - let addr = &(*unerased)._object.msg as *const D as *mut (); - Some(NonNull::new_unchecked(addr)) + let unerased = unsafe { e.cast::>>().as_ref() }; + let addr = NonNull::from(&unerased._object.msg).cast::<()>(); + Some(addr) } else if TypeId::of::() == target { - let unerased = e as *const ErrorImpl<()> as *const ErrorImpl>; - let addr = &(*unerased)._object.error as *const E as *mut (); - Some(NonNull::new_unchecked(addr)) + let unerased = unsafe { e.cast::>>().as_ref() }; + let addr = NonNull::from(&unerased._object.error).cast::<()>(); + Some(addr) } else { None } } -// Safety: requires layout of *e to match ErrorImpl>. -unsafe fn context_drop_rest(e: Box>, target: TypeId) +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl>. +unsafe fn context_downcast_mut( + e: MutPtr<'_, ErrorImpl<()>>, + target: TypeId, +) -> Option> +where + D: 'static, + E: 'static, +{ + if TypeId::of::() == target { + let unerased = unsafe { e.cast::>>().into_mut() }; + let addr = NonNull::from(&unerased._object.msg).cast::<()>(); + Some(addr) + } else if TypeId::of::() == target { + let unerased = unsafe { e.cast::>>().into_mut() }; + let addr = NonNull::from(&mut unerased._object.error).cast::<()>(); + Some(addr) + } else { + None + } +} +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl>. +unsafe fn context_drop_rest(e: OwnedPtr>, 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::() == target { - let unerased = mem::transmute::< - Box>, - Box, E>>>, - >(e); - drop(unerased); + unsafe { + e.cast::, E>>>() + .into_box() + }; } else { - let unerased = mem::transmute::< - Box>, - Box>>>, - >(e); - drop(unerased); + debug_assert_eq!(TypeId::of::(), target); + unsafe { + e.cast::>>>() + .into_box() + }; + } +} + +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl>. +unsafe fn context_chain_downcast( + e: RefPtr<'_, ErrorImpl<()>>, + target: TypeId, +) -> Option> +where + D: 'static, +{ + let unerased = unsafe { e.cast::>>().as_ref() }; + if TypeId::of::() == 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 = &unerased._object.error; + unsafe { (source.vtable().object_downcast)(source.inner.as_ref(), target) } } } -// Safety: requires layout of *e to match ErrorImpl>. -unsafe fn context_chain_downcast(e: &ErrorImpl<()>, target: TypeId) -> Option> +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl>. +unsafe fn context_chain_downcast_mut( + e: MutPtr<'_, ErrorImpl<()>>, + target: TypeId, +) -> Option> where D: 'static, { - let unerased = e as *const ErrorImpl<()> as *const ErrorImpl>; + let unerased = unsafe { e.cast::>>().into_mut() }; if TypeId::of::() == 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 = &mut unerased._object.error; + unsafe { (source.vtable().object_downcast_mut)(source.inner.as_mut(), target) } } } -// Safety: requires layout of *e to match ErrorImpl>. -unsafe fn context_chain_drop_rest(e: Box>, target: TypeId) +/// # Safety +/// +/// Requires layout of *e to match ErrorImpl>. +unsafe fn context_chain_drop_rest(e: OwnedPtr>, 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::() == target { - let unerased = mem::transmute::< - Box>, - Box, Report>>>, - >(e); + let unerased = unsafe { + e.cast::, Report>>>() + .into_box() + }; // Drop the entire rest of the data structure rooted in the next Report. drop(unerased); } else { - let unerased = mem::transmute::< - Box>, - Box>>>, - >(e); - // Read out a ManuallyDrop>> from the next error. - let inner = ptr::read(&unerased._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); + unsafe { + let unerased = e + .cast::>>>() + .into_box(); + // Read out a ManuallyDrop>> from the next error. + let inner = ptr::read(&unerased.as_ref()._object.error.inner); + drop(unerased); + // Recursively drop the next error using the same target typeid. + (header(inner.as_ref()).vtable.object_drop_rest)(inner, target); + } } } -// repr C to ensure that E remains in the final position. #[repr(C)] -pub(crate) struct ErrorImpl { +pub(crate) struct ErrorHeader { vtable: &'static ErrorVTable, pub(crate) handler: Option>, +} + +// repr C to ensure that E remains in the final position. +#[repr(C)] +pub(crate) struct ErrorImpl { + 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 { } impl ErrorImpl { - 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 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 for Box { unsafe { // Read Box> 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'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) } } } diff --git a/src/fmt.rs b/src/fmt.rs index b4e97a3..c66620e 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -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)) } } diff --git a/src/lib.rs b/src/lib.rs index 37885b8..fa5d654 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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>>, + inner: OwnedPtr>, } type ErrorHook = diff --git a/src/ptr.rs b/src/ptr.rs new file mode 100644 index 0000000..3d118e5 --- /dev/null +++ b/src/ptr.rs @@ -0,0 +1,149 @@ +use std::{marker::PhantomData, ptr::NonNull}; + +/// An owned pointer +/// +/// **NOTE**: Does not deallocate when dropped +pub(crate) struct OwnedPtr { + ptr: NonNull, +} + +impl Copy for OwnedPtr {} + +impl Clone for OwnedPtr { + fn clone(&self) -> Self { + *self + } +} + +unsafe impl Send for OwnedPtr where T: Send {} +unsafe impl Sync for OwnedPtr where T: Send {} + +impl OwnedPtr { + pub(crate) fn new(value: T) -> Self { + Self::from_boxed(Box::new(value)) + } + + pub(crate) fn from_boxed(boxed: Box) -> 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(self) -> OwnedPtr { + 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 { + 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, + _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(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, + _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(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() } + } +} diff --git a/src/wrapper.rs b/src/wrapper.rs index f0334e5..b4da68a 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -5,6 +5,9 @@ use core::fmt::{self, Debug, Display}; pub(crate) struct DisplayError(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(pub(crate) M); pub(crate) struct NoneError; diff --git a/tests/compiletest.rs b/tests/compiletest.rs index f9aea23..7974a62 100644 --- a/tests/compiletest.rs +++ b/tests/compiletest.rs @@ -1,4 +1,5 @@ #[rustversion::attr(not(nightly), ignore)] +#[cfg_attr(miri, ignore)] #[test] fn ui() { let t = trybuild::TestCases::new(); diff --git a/tests/test_location.rs b/tests/test_location.rs index d851f5a..58737fe 100644 --- a/tests/test_location.rs +++ b/tests/test_location.rs @@ -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 { + std::fs::read_to_string(path) +} + +#[cfg(miri)] +fn read_path(_path: &str) -> Result { + // 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(); From be31cf59ff1ded6287e330553f55dee2d99ce362 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Fri, 15 Sep 2023 21:50:54 +0100 Subject: [PATCH 4/4] Create foundation for monorepo --- .github/workflows/ci.yml | 15 +++++++++++---- Cargo.toml | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1034a0a..c0d6919 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ on: push: branches: - - master + - master pull_request: {} name: Continuous integration @@ -9,6 +9,10 @@ name: Continuous integration env: MIRIFLAGS: -Zmiri-strict-provenance +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'push' && github.run_number }} + cancel-in-progress: true + jobs: check: name: Check @@ -26,6 +30,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: check + args: --all test-matrix: name: Test Suite @@ -52,7 +57,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: ${{ matrix.features }} + args: --all ${{ matrix.features }} test-msrv: name: Test Suite @@ -74,6 +79,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test + args: --all ${{ matrix.features }} test-os: name: Test Suite @@ -91,6 +97,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test + args: --all fmt: name: Rustfmt @@ -109,7 +116,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: fmt - args: --all -- --check + args: --check clippy: name: Clippy @@ -128,7 +135,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: clippy - args: -- -D warnings + args: --all-targets --all-features -- -D warnings miri: name: Miri runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 8cdfcc3..9090fb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,44 @@ +[workspace] +members = [ + "." +] + +[workspace.package] +authors = ["Jane Lusby "] +edition = "2018" +license = "MIT OR Apache-2.0" +repository = "https://github.com/eyre-rs/eyre" +readme = "README.md" +rust-version = "1.65.0" + +[workspace.dependencies] +indenter = "0.3.0" +once_cell = "1.18.0" + [package] name = "eyre" version = "0.6.8" authors = ["David Tolnay ", "Jane Lusby "] -edition = "2018" -license = "MIT OR Apache-2.0" description = "Flexible concrete Error Reporting type built on std::error::Error with customizable Reports" -repository = "https://github.com/yaahc/eyre" documentation = "https://docs.rs/eyre" -readme = "README.md" categories = ["rust-patterns"] +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +readme = { workspace = true } +rust-version = { workspace = true } + [features] default = ["auto-install", "track-caller"] auto-install = [] track-caller = [] +[dependencies] +indenter = { workspace = true } +once_cell = { workspace = true } +pyo3 = { version = "0.13", optional = true, default-features = false } + [dev-dependencies] futures = { version = "0.3", default-features = false } rustversion = "1.0" @@ -25,11 +49,6 @@ anyhow = "1.0.28" syn = { version = "2.0", features = ["full"] } pyo3 = { version = "0.13", default-features = false, features = ["auto-initialize"] } -[dependencies] -indenter = "0.3.0" -once_cell = "1.18.0" -pyo3 = { version = "0.13", optional = true, default-features = false } - [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] rustdoc-args = ["--cfg", "doc_cfg"]