diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97fcf98..f82f7f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,7 @@ jobs: - 1.36.0 # Oldest supported with MaybeUninit - 1.40.0 # Oldest supported with cfg(doctest) - 1.51.0 # Oldest supported with ptr::addr_of! + - 1.65.0 # Oldest supported with stable const evaluation (sans cell) - stable - beta - nightly @@ -67,8 +68,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - name: Install Miri + run: | + rustup toolchain install nightly --component miri + rustup override set nightly + cargo miri setup - name: Test with Miri - run: ci/miri.sh + run: | + cargo miri test + cargo miri test --all-features style: name: lints and formatting diff --git a/Cargo.toml b/Cargo.toml index 7a62858..71bdc9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "memoffset" -version = "0.6.5" +version = "0.8.0" authors = ["Gilad Naaman "] description = "offset_of functionality for Rust structs." license = "MIT" diff --git a/README.md b/README.md index 9e93c2b..b0bfd10 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ C-Like `offset_of` functionality for Rust structs. Introduces the following macros: * `offset_of!` for obtaining the offset of a member of a struct. * `offset_of_tuple!` for obtaining the offset of a member of a tuple. (Requires Rust 1.20+) + * `offset_of_union!` for obtaining the offset of a member of a union. * `span_of!` for obtaining the range that a field, or fields, span. `memoffset` works under `no_std` environments. @@ -16,7 +17,7 @@ Add the following dependency to your `Cargo.toml`: ```toml [dependencies] -memoffset = "0.6" +memoffset = "0.8" ``` These versions will compile fine with rustc versions greater or equal to 1.19. @@ -45,20 +46,39 @@ fn main() { } ``` -## Feature flags ## +## Usage in constants ## +`memoffset` has support for compile-time `offset_of!` on rust>=1.65, or on older nightly compilers. -### Usage in constants ### -`memoffset` has **experimental** support for compile-time `offset_of!` on a nightly compiler. +### Usage on stable Rust ### +Constant evaluation is automatically enabled and avilable on stable compilers starting with rustc 1.65. -In order to use it, you must enable the `unstable_const` crate feature and several compiler features. +This is an incomplete implementation with one caveat: +Due to dependence on [`#![feature(const_refs_to_cell)]`](https://github.com/rust-lang/rust/issues/80384), you cannot get the offset of a `Cell` field in a const-context. + +This means that if need to get the offset of a cell, you'll have to remain on nightly for now. + +### Usage on recent nightlies ### + +If you're using a new-enough nightly and you require the ability to get the offset of a `Cell`, +you'll have to enable the `unstable_const` cargo feature, as well as enabling `const_refs_to_cell` in your crate root. + +Do note that `unstable_const` is an unstable feature that is set to be removed in a future version of `memoffset`. Cargo.toml: ```toml [dependencies.memoffset] -version = "0.6" +version = "0.8" features = ["unstable_const"] ``` +Your crate root: (`lib.rs`/`main.rs`) +```rust,ignore +#![feature(const_refs_to_cell)] +``` + +### Usage on older nightlies ### +In order to use it on an older nightly compiler, you must enable the `unstable_const` crate feature and several compiler features. + Your crate root: (`lib.rs`/`main.rs`) ```rust,ignore #![feature(const_ptr_offset_from, const_refs_to_cell)] diff --git a/build.rs b/build.rs index 0604c19..e18810f 100644 --- a/build.rs +++ b/build.rs @@ -19,4 +19,7 @@ fn main() { if ac.probe_rustc_version(1, 51) { println!("cargo:rustc-cfg=raw_ref_macros"); } + if ac.probe_rustc_version(1, 65) { + println!("cargo:rustc-cfg=stable_const"); + } } diff --git a/ci/miri.sh b/ci/miri.sh deleted file mode 100755 index 5aea2ec..0000000 --- a/ci/miri.sh +++ /dev/null @@ -1,14 +0,0 @@ -set -ex - -# Install Miri. -MIRI_NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri) -echo "Installing latest nightly with Miri: $MIRI_NIGHTLY" -rustup default "$MIRI_NIGHTLY" -rustup component add miri - -# Run tests. -cargo miri test -cargo miri test --all-features - -# Restore old state in case Travis uses this cache for other jobs. -rustup default nightly diff --git a/src/lib.rs b/src/lib.rs index d80ff17..72736aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,9 +57,10 @@ #![no_std] #![cfg_attr( - feature = "unstable_const", - feature(const_ptr_offset_from, const_refs_to_cell) + all(feature = "unstable_const", not(stable_const)), + feature(const_ptr_offset_from) )] +#![cfg_attr(feature = "unstable_const", feature(const_refs_to_cell))] #[macro_use] #[cfg(doctests)] diff --git a/src/offset_of.rs b/src/offset_of.rs index 8596e45..9ce4ae2 100644 --- a/src/offset_of.rs +++ b/src/offset_of.rs @@ -46,7 +46,7 @@ macro_rules! _memoffset__let_base_ptr { } /// Macro to compute the distance between two pointers. -#[cfg(feature = "unstable_const")] +#[cfg(any(feature = "unstable_const", stable_const))] #[macro_export] #[doc(hidden)] macro_rules! _memoffset_offset_from_unsafe { @@ -58,7 +58,7 @@ macro_rules! _memoffset_offset_from_unsafe { unsafe { (field as *const u8).offset_from(base as *const u8) as usize } }}; } -#[cfg(not(feature = "unstable_const"))] +#[cfg(not(any(feature = "unstable_const", stable_const)))] #[macro_export] #[doc(hidden)] macro_rules! _memoffset_offset_from_unsafe { @@ -86,6 +86,16 @@ macro_rules! _memoffset_offset_from_unsafe { /// assert_eq!(offset_of!(Foo, b), 4); /// } /// ``` +/// +/// ## Notes +/// Rust's ABI is unstable, and [type layout can be changed with each +/// compilation](https://doc.rust-lang.org/reference/type-layout.html). +/// +/// Using `offset_of!` with a `repr(Rust)` struct will return the correct offset of the +/// specified `field` for a particular compilation, but the exact value may change +/// based on the compiler version, concrete struct type, time of day, or rustc's mood. +/// +/// As a result, the value should not be retained and used between different compilations. #[macro_export(local_inner_macros)] macro_rules! offset_of { ($parent:path, $field:tt) => {{ @@ -121,6 +131,39 @@ macro_rules! offset_of_tuple { }}; } +/// Calculates the offset of the specified union member from the start of the union. +/// +/// ## Examples +/// ``` +/// use memoffset::offset_of_union; +/// +/// #[repr(C, packed)] +/// union Foo { +/// foo32: i32, +/// foo64: i64, +/// } +/// +/// fn main() { +/// assert!(offset_of_union!(Foo, foo64) == 0); +/// } +/// ``` +/// +/// ## Note +/// Due to macro_rules limitations, this macro will accept structs with a single field as well as unions. +/// This is not a stable guarantee, and future versions of this crate might fail +/// on any use of this macro with a struct, without a semver bump. +#[macro_export(local_inner_macros)] +macro_rules! offset_of_union { + ($parent:path, $field:tt) => {{ + // Get a base pointer (non-dangling if rustc supports `MaybeUninit`). + _memoffset__let_base_ptr!(base_ptr, $parent); + // Get field pointer. + let field_ptr = raw_field_union!(base_ptr, $parent, $field); + // Compute offset. + _memoffset_offset_from_unsafe!(field_ptr, base_ptr) + }}; +} + #[cfg(test)] mod tests { #[test] @@ -161,6 +204,21 @@ mod tests { assert_eq!(offset_of!(Tup, 1), 4); } + #[test] + fn offset_union() { + // Since we're specifying repr(C), all fields are supposed to be at offset 0 + #[repr(C)] + union Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + assert_eq!(offset_of_union!(Foo, a), 0); + assert_eq!(offset_of_union!(Foo, b), 0); + assert_eq!(offset_of_union!(Foo, c), 0); + } + #[test] fn path() { mod sub { @@ -238,7 +296,23 @@ mod tests { ); } - #[cfg(feature = "unstable_const")] + #[test] + fn test_raw_field_union() { + #[repr(C)] + union Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + let f = Foo { a: 0 }; + let f_ptr = &f as *const _; + assert_eq!(f_ptr as usize + 0, raw_field_union!(f_ptr, Foo, a) as usize); + assert_eq!(f_ptr as usize + 0, raw_field_union!(f_ptr, Foo, b) as usize); + assert_eq!(f_ptr as usize + 0, raw_field_union!(f_ptr, Foo, c) as usize); + } + + #[cfg(any(feature = "unstable_const", stable_const))] #[test] fn const_offset() { #[repr(C)] @@ -263,7 +337,7 @@ mod tests { assert_eq!([0; offset_of!(Foo, b)].len(), 4); } - #[cfg(feature = "unstable_const")] + #[cfg(any(feature = "unstable_const", stable_const))] #[test] fn const_fn_offset() { const fn test_fn() -> usize { diff --git a/src/raw_field.rs b/src/raw_field.rs index a8dd2b3..e16df9f 100644 --- a/src/raw_field.rs +++ b/src/raw_field.rs @@ -39,6 +39,21 @@ macro_rules! _memoffset__addr_of { } /// Deref-coercion protection macro. +/// +/// Prevents complilation if the specified field name is not a part of the +/// struct definition. +/// +/// ```compile_fail +/// use memoffset::_memoffset__field_check; +/// +/// struct Foo { +/// foo: i32, +/// } +/// +/// type BoxedFoo = Box; +/// +/// _memoffset__field_check!(BoxedFoo, foo); +/// ``` #[cfg(allow_clippy)] #[macro_export] #[doc(hidden)] @@ -64,6 +79,25 @@ macro_rules! _memoffset__field_check { } /// Deref-coercion protection macro. +/// +/// Prevents complilation if the specified type is not a tuple. +/// +/// ```compile_fail +/// use memoffset::_memoffset__field_check_tuple; +/// +/// _memoffset__field_check_tuple!(i32, 0); +/// ``` +#[cfg(allow_clippy)] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__field_check_tuple { + ($type:ty, $field:tt) => { + // Make sure the type argument is a tuple + #[allow(clippy::unneeded_wildcard_pattern)] + let (_, ..): $type; + }; +} +#[cfg(not(allow_clippy))] #[macro_export] #[doc(hidden)] macro_rules! _memoffset__field_check_tuple { @@ -73,6 +107,53 @@ macro_rules! _memoffset__field_check_tuple { }; } +/// Deref-coercion protection macro for unions. +/// Unfortunately accepts single-field structs as well, which is not ideal, +/// but ultimately pretty harmless. +/// +/// ```compile_fail +/// use memoffset::_memoffset__field_check_union; +/// +/// union Foo { +/// variant_a: i32, +/// } +/// +/// type BoxedFoo = Box; +/// +/// _memoffset__field_check_union!(BoxedFoo, variant_a); +/// ``` +#[cfg(allow_clippy)] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__field_check_union { + ($type:path, $field:tt) => { + // Make sure the field actually exists. This line ensures that a + // compile-time error is generated if $field is accessed through a + // Deref impl. + #[allow(clippy::unneeded_wildcard_pattern)] + // rustc1.19 requires unsafe here for the pattern; not needed in newer versions + #[allow(unused_unsafe)] + unsafe { + let $type { $field: _ }; + } + }; +} +#[cfg(not(allow_clippy))] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__field_check_union { + ($type:path, $field:tt) => { + // Make sure the field actually exists. This line ensures that a + // compile-time error is generated if $field is accessed through a + // Deref impl. + // rustc1.19 requires unsafe here for the pattern; not needed in newer versions + #[allow(unused_unsafe)] + unsafe { + let $type { $field: _ }; + } + }; +} + /// Computes a const raw pointer to the given field of the given base pointer /// to the given parent type. /// @@ -115,3 +196,31 @@ macro_rules! raw_field_tuple { } }}; } + +/// Computes a const raw pointer to the given field of the given base pointer +/// to the given parent tuple typle. +/// +/// The `base` pointer *must not* be dangling, but it *may* point to +/// uninitialized memory. +/// +/// ## Note +/// This macro is the same as `raw_field`, except for a different Deref-coercion check that +/// supports unions. +/// Due to macro_rules limitations, this check will accept structs with a single field as well as unions. +/// This is not a stable guarantee, and future versions of this crate might fail +/// on any use of this macro with a struct, without a semver bump. +#[macro_export(local_inner_macros)] +macro_rules! raw_field_union { + ($base:expr, $parent:path, $field:tt) => {{ + _memoffset__field_check_union!($parent, $field); + let base = $base; // evaluate $base outside the `unsafe` block + + // Get the field address. + // Crucially, we know that this will not trigger a deref coercion because + // of the field check we did above. + #[allow(unused_unsafe)] // for when the macro is used in an unsafe block + unsafe { + _memoffset__addr_of!((*(base as *const $parent)).$field) + } + }}; +} diff --git a/src/span_of.rs b/src/span_of.rs index aab9d0a..89fccce 100644 --- a/src/span_of.rs +++ b/src/span_of.rs @@ -52,11 +52,18 @@ macro_rules! _memoffset__compile_error { /// span_of!(Struct, start ..) /// ``` /// -/// *Note*: +/// ### Note /// This macro uses recursion in order to resolve the range expressions, so there is a limit to /// the complexity of the expression. /// In order to raise the limit, the compiler's recursion limit should be lifted. /// +/// ### Safety +/// The inter-field form mentioned above assumes that the first field is positioned before the +/// second. +/// This is only guarenteed for `repr(C)` structs. +/// Usage with `repr(Rust)` structs may yield unexpected results, like downward-going ranges, +/// spans that include unexpected fields, empty spans, or spans that include *unexpected* padding bytes. +/// /// ## Examples /// ``` /// use memoffset::span_of;