diff options
author | Victor Hsieh <victorhsieh@google.com> | 2021-01-06 23:17:41 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-01-06 23:17:41 +0000 |
commit | 71401dbd4a2829c8437b757cef5f62c2b113bfcc (patch) | |
tree | 0f71108f0df4dec57714cdde613b9bf02ef490bf | |
parent | 3037f986244f7a025bc269c67f1053a808b72e30 (diff) | |
parent | 8d7c8b7f620e3365697e01a97bcc9d20648b6fe0 (diff) | |
download | memoffset-71401dbd4a2829c8437b757cef5f62c2b113bfcc.tar.gz |
Import memoffset from crates.io am: f32bc367f4 am: 9f4a1dac7e am: 8d7c8b7f62
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/memoffset/+/1530422
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I6f8d6de810e466fc9d4a4da7ad9fcd7a94a6c6f0
-rw-r--r-- | .cargo_vcs_info.json | 5 | ||||
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | .travis.yml | 47 | ||||
-rw-r--r-- | Android.bp | 50 | ||||
-rw-r--r-- | Cargo.toml | 31 | ||||
-rw-r--r-- | Cargo.toml.orig | 21 | ||||
-rw-r--r-- | LICENSE | 19 | ||||
-rw-r--r-- | METADATA | 19 | ||||
-rw-r--r-- | MODULE_LICENSE_MIT | 0 | ||||
-rw-r--r-- | OWNERS | 1 | ||||
-rw-r--r-- | README.md | 80 | ||||
-rw-r--r-- | TEST_MAPPING | 12 | ||||
-rw-r--r-- | build.rs | 19 | ||||
-rw-r--r-- | ci/miri.sh | 14 | ||||
-rw-r--r-- | patches/Android.bp.patch | 14 | ||||
-rw-r--r-- | patches/std.diff | 40 | ||||
-rw-r--r-- | src/lib.rs | 93 | ||||
-rw-r--r-- | src/offset_of.rs | 270 | ||||
-rw-r--r-- | src/raw_field.rs | 115 | ||||
-rw-r--r-- | src/span_of.rs | 264 |
20 files changed, 1119 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..3621e2c --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "da57ff39e1007a5cb2b50380bb235d067b648846" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e06a7f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target/ +**/*.rs.bk +Cargo.lock +.idea/ +cmake-build-debug/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1bf5ede --- /dev/null +++ b/.travis.yml @@ -0,0 +1,47 @@ +sudo: false +language: rust +cache: + cargo: true +matrix: + include: + - name: miri + env: TRAVIS_MIRI_JOB # make sure the cache is not shared with other "nightly" jobs + rust: nightly + script: + - sh ci/miri.sh + + - rust: 1.19.0 # Oldest supported (first version with numeric fields in struct patterns) + - rust: 1.31.0 # Oldest supported with allow(clippy) + - rust: 1.36.0 # Oldest supported with MaybeUninit + - rust: stable + - rust: beta + - rust: nightly + + - name: all-features + rust: nightly + script: + # `--lib` added to prevent doctests from being compiled. + # This is due to `unstable_const` requiring extra `feature(...)` directives + # which the doctests do not have. + - cargo test --verbose --all-features --lib + + - name: rustfmt + rust: 1.36.0 + install: + - rustup component add rustfmt + script: + - cargo fmt -- --check + + - name: deny-warnings + env: RUSTFLAGS="-D warnings" + rust: 1.33.0 # `stable`: Locking down for consistent behavior + script: + - cargo check --tests + +install: +- rustc -Vv +- cargo -V + +script: +- rm -rf target/debug/deps/*memoffset* # Avoid rustdoc problems +- cargo test --verbose diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..37f0f91 --- /dev/null +++ b/Android.bp @@ -0,0 +1,50 @@ +// This file is generated by cargo2android.py --run --device --tests --dependencies --patch=patches/Android.bp.patch. + +rust_library { + name: "libmemoffset", + host_supported: true, + crate_name: "memoffset", + srcs: ["src/lib.rs"], + edition: "2015", + features: ["default"], + flags: [ + "--cfg allow_clippy", + "--cfg doctests", + "--cfg maybe_uninit", + "--cfg tuple_ty", + ], +} + +rust_defaults { + name: "memoffset_defaults", + crate_name: "memoffset", + srcs: ["src/lib.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + edition: "2015", + features: ["default"], + flags: [ + "--cfg allow_clippy", + "--cfg doctests", + "--cfg maybe_uninit", + "--cfg tuple_ty", + ], + rustlibs: [ + // ANDROID: disable for now just to avoid another dependency. + //"libdoc_comment", + ], +} + +rust_test_host { + name: "memoffset_host_test_src_lib", + defaults: ["memoffset_defaults"], +} + +rust_test { + name: "memoffset_device_test_src_lib", + defaults: ["memoffset_defaults"], +} + +// dependent_library ["feature_list"] +// autocfg-1.0.1 +// doc-comment-0.3.3 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9e2874f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "memoffset" +version = "0.6.1" +authors = ["Gilad Naaman <gilad.naaman@gmail.com>"] +description = "offset_of functionality for Rust structs." +readme = "README.md" +keywords = ["mem", "offset", "offset_of", "offsetof"] +categories = ["no-std"] +license = "MIT" +repository = "https://github.com/Gilnaa/memoffset" +[dev-dependencies.doc-comment] +version = "0.3" +[build-dependencies.autocfg] +version = "1" + +[features] +default = [] +unstable_const = [] +unstable_raw = [] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..61e20d9 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,21 @@ +[package] +name = "memoffset" +version = "0.6.1" +authors = ["Gilad Naaman <gilad.naaman@gmail.com>"] +description = "offset_of functionality for Rust structs." +license = "MIT" +readme = "README.md" +repository = "https://github.com/Gilnaa/memoffset" +keywords = ["mem", "offset", "offset_of", "offsetof"] +categories = ["no-std"] + +[build-dependencies] +autocfg = "1" + +[dev-dependencies] +doc-comment = "0.3" + +[features] +default = [] +unstable_const = [] +unstable_raw = [] @@ -0,0 +1,19 @@ +Copyright (c) 2017 Gilad Naaman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
\ No newline at end of file diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..c803657 --- /dev/null +++ b/METADATA @@ -0,0 +1,19 @@ +name: "memoffset" +description: "offset_of functionality for Rust structs." +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/memoffset" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/memoffset/memoffset-0.6.1.crate" + } + version: "0.6.1" + license_type: NOTICE + last_upgrade_date { + year: 2020 + month: 12 + day: 15 + } +} diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_MIT @@ -0,0 +1 @@ +include platform/prebuilts/rust:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..67ce9eb --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# memoffset # + +[![](http://meritbadge.herokuapp.com/memoffset)](https://crates.io/crates/memoffset) + +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+) + * `span_of!` for obtaining the range that a field, or fields, span. + +`memoffset` works under `no_std` environments. + +## Usage ## +Add the following dependency to your `Cargo.toml`: + +```toml +[dependencies] +memoffset = "0.6" +``` + +These versions will compile fine with rustc versions greater or equal to 1.19. + +Add the following lines at the top of your `main.rs` or `lib.rs` files. + +```rust,ignore +#[macro_use] +extern crate memoffset; +``` + +## Examples ## +```rust +#[macro_use] +extern crate memoffset; + +#[repr(C, packed)] +struct Foo { + a: u32, + b: u32, + c: [u8; 5], + d: u32, +} + +fn main() { + assert_eq!(offset_of!(Foo, b), 4); + assert_eq!(offset_of!(Foo, d), 4+4+5); + + assert_eq!(span_of!(Foo, a), 0..4); + assert_eq!(span_of!(Foo, a .. c), 0..8); + assert_eq!(span_of!(Foo, a ..= c), 0..13); + assert_eq!(span_of!(Foo, ..= d), 0..17); + assert_eq!(span_of!(Foo, b ..), 4..17); +} +``` + +## Feature flags ## + +### Usage in constants ### +`memoffset` has **experimental** support for compile-time `offset_of!` on a nightly compiler. + +In order to use it, you must enable the `unstable_const` crate feature and several compiler features. + +Cargo.toml: +```toml +[dependencies.memoffset] +version = "0.6" +features = ["unstable_const"] +``` + +Your crate root: (`lib.rs`/`main.rs`) +```rust,ignore +#![feature(ptr_offset_from, const_ptr_offset_from, const_maybe_uninit_as_ptr, const_raw_ptr_deref)] +``` + +If you intend to use `offset_of!` inside a `const fn`, also add the `const_fn` compiler feature. + +### Raw references ### +Recent nightlies support [a way to create raw pointers](https://github.com/rust-lang/rust/issues/73394) that avoids creating intermediate safe references. +`memoffset` can make use of that feature to avoid what is technically Undefined Behavior. +Use the `unstable_raw` feature to enable this. diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 0000000..0bfec90 --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,12 @@ +// Generated by cargo2android.py for tests in Android.bp +{ + "presubmit": [ + { + "host": true, + "name": "memoffset_host_test_src_lib" + }, + { + "name": "memoffset_device_test_src_lib" + } + ] +} diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..c83bff2 --- /dev/null +++ b/build.rs @@ -0,0 +1,19 @@ +extern crate autocfg; + +fn main() { + let ac = autocfg::new(); + + // Check for a minimum version for a few features + if ac.probe_rustc_version(1, 20) { + println!("cargo:rustc-cfg=tuple_ty"); + } + if ac.probe_rustc_version(1, 31) { + println!("cargo:rustc-cfg=allow_clippy"); + } + if ac.probe_rustc_version(1, 36) { + println!("cargo:rustc-cfg=maybe_uninit"); + } + if ac.probe_rustc_version(1, 40) { + println!("cargo:rustc-cfg=doctests"); + } +} diff --git a/ci/miri.sh b/ci/miri.sh new file mode 100644 index 0000000..5aea2ec --- /dev/null +++ b/ci/miri.sh @@ -0,0 +1,14 @@ +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/patches/Android.bp.patch b/patches/Android.bp.patch new file mode 100644 index 0000000..6acbe2c --- /dev/null +++ b/patches/Android.bp.patch @@ -0,0 +1,14 @@ +diff --git a/Android.bp b/Android.bp +index fafdf41..81766e3 100644 +--- a/Android.bp ++++ b/Android.bp +@@ -30,7 +30,8 @@ rust_defaults { + "--cfg tuple_ty", + ], + rustlibs: [ +- "libdoc_comment", ++ // ANDROID: disable for now just to avoid another dependency. ++ //"libdoc_comment", + ], + } + diff --git a/patches/std.diff b/patches/std.diff new file mode 100644 index 0000000..8238bdd --- /dev/null +++ b/patches/std.diff @@ -0,0 +1,40 @@ +diff --git a/src/lib.rs b/src/lib.rs +index c85fb01..25b0444 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -56,7 +56,8 @@ + //! let checksum = crc16(checksum_range); + //! ``` + +-#![no_std] ++// ANDROID: include standard library to build as a dynlib ++//#![no_std] + #![cfg_attr( + feature = "unstable_const", + feature( +@@ -80,9 +81,9 @@ doctest!("../README.md"); + // This `use` statement enables the macros to use `$crate::mem`. + // Doing this enables this crate to function under both std and no-std crates. + #[doc(hidden)] +-pub use core::mem; ++pub use std::mem; // ANDROID: use std instead of core, since we're not building wiht no-std. + #[doc(hidden)] +-pub use core::ptr; ++pub use std::ptr; // ANDROID: use std instead of core, since we're not building wiht no-std. + + #[macro_use] + mod raw_field; +diff --git a/src/span_of.rs b/src/span_of.rs +index 0592dbd..369e5c6 100644 +--- a/src/span_of.rs ++++ b/src/span_of.rs +@@ -155,7 +155,8 @@ macro_rules! span_of { + + #[cfg(test)] + mod tests { +- use core::mem; ++ // ANDROID: use std instead of core, since we're not building wiht no-std. ++ use std::mem; + + #[test] + fn span_simple() { diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..25b0444 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,93 @@ +// Copyright (c) 2017 Gilad Naaman +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! A crate used for calculating offsets of struct members and their spans. +//! +//! This functionality currently can not be used in compile time code such as `const` or `const fn` definitions. +//! +//! ## Examples +//! ``` +//! #[macro_use] +//! extern crate memoffset; +//! +//! #[repr(C, packed)] +//! struct HelpMeIAmTrappedInAStructFactory { +//! help_me_before_they_: [u8; 15], +//! a: u32 +//! } +//! +//! fn main() { +//! assert_eq!(offset_of!(HelpMeIAmTrappedInAStructFactory, a), 15); +//! assert_eq!(span_of!(HelpMeIAmTrappedInAStructFactory, a), 15..19); +//! assert_eq!(span_of!(HelpMeIAmTrappedInAStructFactory, help_me_before_they_ .. a), 0..15); +//! } +//! ``` +//! +//! This functionality can be useful, for example, for checksum calculations: +//! +//! ```ignore +//! #[repr(C, packed)] +//! struct Message { +//! header: MessageHeader, +//! fragment_index: u32, +//! fragment_count: u32, +//! payload: [u8; 1024], +//! checksum: u16 +//! } +//! +//! let checksum_range = &raw[span_of!(Message, header..checksum)]; +//! let checksum = crc16(checksum_range); +//! ``` + +// ANDROID: include standard library to build as a dynlib +//#![no_std] +#![cfg_attr( + feature = "unstable_const", + feature( + ptr_offset_from, + const_fn, + const_ptr_offset_from, + const_maybe_uninit_as_ptr, + const_raw_ptr_deref, + ) +)] +#![cfg_attr(feature = "unstable_raw", feature(raw_ref_macros))] + +#[macro_use] +#[cfg(doctests)] +#[cfg(doctest)] +extern crate doc_comment; +#[cfg(doctests)] +#[cfg(doctest)] +doctest!("../README.md"); + +// This `use` statement enables the macros to use `$crate::mem`. +// Doing this enables this crate to function under both std and no-std crates. +#[doc(hidden)] +pub use std::mem; // ANDROID: use std instead of core, since we're not building wiht no-std. +#[doc(hidden)] +pub use std::ptr; // ANDROID: use std instead of core, since we're not building wiht no-std. + +#[macro_use] +mod raw_field; +#[macro_use] +mod offset_of; +#[macro_use] +mod span_of; diff --git a/src/offset_of.rs b/src/offset_of.rs new file mode 100644 index 0000000..2ffb79c --- /dev/null +++ b/src/offset_of.rs @@ -0,0 +1,270 @@ +// Copyright (c) 2017 Gilad Naaman +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +/// Macro to create a local `base_ptr` raw pointer of the given type, avoiding UB as +/// much as is possible currently. +#[cfg(maybe_uninit)] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__let_base_ptr { + ($name:ident, $type:ty) => { + // No UB here, and the pointer does not dangle, either. + // But we have to make sure that `uninit` lives long enough, + // so it has to be in the same scope as `$name`. That's why + // `let_base_ptr` declares a variable (several, actually) + // instead of returning one. + let uninit = $crate::mem::MaybeUninit::<$type>::uninit(); + let $name: *const $type = uninit.as_ptr(); + }; +} +#[cfg(not(maybe_uninit))] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__let_base_ptr { + ($name:ident, $type:ty) => { + // No UB right here, but we will later dereference this pointer to + // offset into a field, and that is UB because the pointer is dangling. + let $name = $crate::mem::align_of::<$type>() as *const $type; + }; +} + +/// Macro to compute the distance between two pointers. +#[cfg(feature = "unstable_const")] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset_offset_from { + ($field:expr, $base:expr) => { + // Compute offset, with unstable `offset_from` for const-compatibility. + // (Requires the pointers to not dangle, but we already need that for `raw_field!` anyway.) + unsafe { ($field as *const u8).offset_from($base as *const u8) as usize } + }; +} +#[cfg(not(feature = "unstable_const"))] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset_offset_from { + ($field:expr, $base:expr) => { + // Compute offset. + ($field as usize) - ($base as usize) + }; +} + +/// Calculates the offset of the specified field from the start of the named struct. +/// +/// ## Examples +/// ``` +/// #[macro_use] +/// extern crate memoffset; +/// +/// #[repr(C, packed)] +/// struct Foo { +/// a: u32, +/// b: u64, +/// c: [u8; 5] +/// } +/// +/// fn main() { +/// assert_eq!(offset_of!(Foo, a), 0); +/// assert_eq!(offset_of!(Foo, b), 4); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! offset_of { + ($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!(base_ptr, $parent, $field); + // Compute offset. + _memoffset_offset_from!(field_ptr, base_ptr) + }}; +} + +/// Calculates the offset of the specified field from the start of the tuple. +/// +/// ## Examples +/// ``` +/// #[macro_use] +/// extern crate memoffset; +/// +/// fn main() { +/// assert!(offset_of_tuple!((u8, u32), 1) >= 0, "Tuples do not have a defined layout"); +/// } +/// ``` +#[cfg(tuple_ty)] +#[macro_export(local_inner_macros)] +macro_rules! offset_of_tuple { + ($parent:ty, $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_tuple!(base_ptr, $parent, $field); + // Compute offset. + _memoffset_offset_from!(field_ptr, base_ptr) + }}; +} + +#[cfg(test)] +mod tests { + #[test] + fn offset_simple() { + #[repr(C)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + assert_eq!(offset_of!(Foo, a), 0); + assert_eq!(offset_of!(Foo, b), 4); + assert_eq!(offset_of!(Foo, c), 8); + } + + #[test] + #[cfg_attr(miri, ignore)] // this creates unaligned references + fn offset_simple_packed() { + #[repr(C, packed)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + assert_eq!(offset_of!(Foo, a), 0); + assert_eq!(offset_of!(Foo, b), 4); + assert_eq!(offset_of!(Foo, c), 6); + } + + #[test] + fn tuple_struct() { + #[repr(C)] + struct Tup(i32, i32); + + assert_eq!(offset_of!(Tup, 0), 0); + assert_eq!(offset_of!(Tup, 1), 4); + } + + #[test] + fn path() { + mod sub { + #[repr(C)] + pub struct Foo { + pub x: u32, + } + } + + assert_eq!(offset_of!(sub::Foo, x), 0); + } + + #[test] + fn inside_generic_method() { + struct Pair<T, U>(T, U); + + fn foo<T, U>(_: Pair<T, U>) -> usize { + offset_of!(Pair<T, U>, 1) + } + + assert_eq!(foo(Pair(0, 0)), 4); + } + + #[cfg(tuple_ty)] + #[test] + fn test_tuple_offset() { + let f = (0i32, 0.0f32, 0u8); + let f_ptr = &f as *const _; + let f1_ptr = &f.1 as *const _; + + assert_eq!( + f1_ptr as usize - f_ptr as usize, + offset_of_tuple!((i32, f32, u8), 1) + ); + } + + #[test] + fn test_raw_field() { + #[repr(C)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + let f: Foo = Foo { + a: 0, + b: [0, 0], + c: 0, + }; + let f_ptr = &f as *const _; + assert_eq!(f_ptr as usize + 0, raw_field!(f_ptr, Foo, a) as usize); + assert_eq!(f_ptr as usize + 4, raw_field!(f_ptr, Foo, b) as usize); + assert_eq!(f_ptr as usize + 8, raw_field!(f_ptr, Foo, c) as usize); + } + + #[cfg(tuple_ty)] + #[test] + fn test_raw_field_tuple() { + let t = (0u32, 0u8, false); + let t_ptr = &t as *const _; + let t_addr = t_ptr as usize; + + assert_eq!( + &t.0 as *const _ as usize - t_addr, + raw_field_tuple!(t_ptr, (u32, u8, bool), 0) as usize - t_addr + ); + assert_eq!( + &t.1 as *const _ as usize - t_addr, + raw_field_tuple!(t_ptr, (u32, u8, bool), 1) as usize - t_addr + ); + assert_eq!( + &t.2 as *const _ as usize - t_addr, + raw_field_tuple!(t_ptr, (u32, u8, bool), 2) as usize - t_addr + ); + } + + #[cfg(feature = "unstable_const")] + #[test] + fn const_offset() { + #[repr(C)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + assert_eq!([0; offset_of!(Foo, b)].len(), 4); + } + + #[cfg(feature = "unstable_const")] + #[test] + fn const_fn_offset() { + const fn test_fn() -> usize { + #[repr(C)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + offset_of!(Foo, b) + } + + assert_eq!([0; test_fn()].len(), 4); + } +} diff --git a/src/raw_field.rs b/src/raw_field.rs new file mode 100644 index 0000000..16d01bb --- /dev/null +++ b/src/raw_field.rs @@ -0,0 +1,115 @@ +// Copyright (c) 2020 Gilad Naaman, Ralf Jung +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +/// `raw_const!`, or just ref-then-cast when that is not available. +#[cfg(feature = "unstable_raw")] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__raw_const { + ($path:expr) => {{ + $crate::ptr::raw_const!($path) + }}; +} +#[cfg(not(feature = "unstable_raw"))] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__raw_const { + ($path:expr) => {{ + // This is UB because we create an intermediate reference to uninitialized memory. + // Nothing we can do about that without `raw_const!` though. + &$path as *const _ + }}; +} + +/// Deref-coercion protection macro. +#[cfg(allow_clippy)] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__field_check { + ($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_field_pattern)] + let $type { $field: _, .. }; + }; +} +#[cfg(not(allow_clippy))] +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__field_check { + ($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. + let $type { $field: _, .. }; + }; +} + +/// Deref-coercion protection macro. +#[macro_export] +#[doc(hidden)] +macro_rules! _memoffset__field_check_tuple { + ($type:ty, $field:tt) => { + // Make sure the type argument is a tuple + let (_, ..): $type; + }; +} + +/// Computes a const raw pointer to the given field of the given base pointer +/// to the given parent type. +/// +/// The `base` pointer *must not* be dangling, but it *may* point to +/// uninitialized memory. +#[macro_export(local_inner_macros)] +macro_rules! raw_field { + ($base:expr, $parent:path, $field:tt) => {{ + _memoffset__field_check!($parent, $field); + + // 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__raw_const!((*($base as *const $parent)).$field) + } + }}; +} + +/// 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. +#[cfg(tuple_ty)] +#[macro_export(local_inner_macros)] +macro_rules! raw_field_tuple { + ($base:expr, $parent:ty, $field:tt) => {{ + _memoffset__field_check_tuple!($parent, $field); + + // 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__raw_const!((*($base as *const $parent)).$field) + } + }}; +} diff --git a/src/span_of.rs b/src/span_of.rs new file mode 100644 index 0000000..369e5c6 --- /dev/null +++ b/src/span_of.rs @@ -0,0 +1,264 @@ +// Copyright (c) 2017 Gilad Naaman +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +/// Reexport for `local_inner_macros`; see +/// <https://doc.rust-lang.org/edition-guide/rust-2018/macros/macro-changes.html#macros-using-local_inner_macros>. +#[doc(hidden)] +#[macro_export] +macro_rules! _memoffset__compile_error { + ($($inner:tt)*) => { + compile_error! { $($inner)* } + } +} + +/// Produces a range instance representing the sub-slice containing the specified member. +/// +/// This macro provides 2 forms of differing functionalities. +/// +/// The first form is identical to the appearance of the `offset_of!` macro. +/// +/// ```ignore +/// span_of!(Struct, member) +/// ``` +/// +/// The second form of `span_of!` returns a sub-slice which starts at one field, and ends at another. +/// The general pattern of this form is: +/// +/// ```ignore +/// // Exclusive +/// span_of!(Struct, member_a .. member_b) +/// // Inclusive +/// span_of!(Struct, member_a ..= member_b) +/// +/// // Open-ended ranges +/// span_of!(Struct, .. end) +/// span_of!(Struct, start ..) +/// ``` +/// +/// *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. +/// +/// ## Examples +/// ``` +/// #[macro_use] +/// extern crate memoffset; +/// +/// #[repr(C)] +/// struct Florp { +/// a: u32 +/// } +/// +/// #[repr(C)] +/// struct Blarg { +/// x: [u32; 2], +/// y: [u8; 56], +/// z: Florp, +/// egg: [[u8; 4]; 4] +/// } +/// +/// fn main() { +/// assert_eq!(0..84, span_of!(Blarg, ..)); +/// assert_eq!(0..8, span_of!(Blarg, .. y)); +/// assert_eq!(0..64, span_of!(Blarg, ..= y)); +/// assert_eq!(0..8, span_of!(Blarg, x)); +/// assert_eq!(8..84, span_of!(Blarg, y ..)); +/// assert_eq!(0..8, span_of!(Blarg, x .. y)); +/// assert_eq!(0..64, span_of!(Blarg, x ..= y)); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! span_of { + (@helper $root:ident, [] ..=) => { + _memoffset__compile_error!("Expected a range, found '..='") + }; + (@helper $root:ident, [] ..) => { + _memoffset__compile_error!("Expected a range, found '..'") + }; + // No explicit begin for range. + (@helper $root:ident, $parent:path, [] ..) => {{ + // UB due to taking references to uninitialized fields for `size_of_val`. + ($root as usize, + $root as usize + $crate::mem::size_of_val(&(*$root))) + }}; + (@helper $root:ident, $parent:path, [] ..= $field:tt) => {{ + // UB due to taking references to uninitialized fields for `size_of_val`. + ($root as usize, + raw_field!($root, $parent, $field) as usize + $crate::mem::size_of_val(&(*$root).$field)) + }}; + (@helper $root:ident, $parent:path, [] .. $field:tt) => {{ + ($root as usize, raw_field!($root, $parent, $field) as usize) + }}; + // Explicit begin and end for range. + (@helper $root:ident, $parent:path, # $begin:tt [] ..= $end:tt) => {{ + // UB due to taking references to uninitialized fields for `size_of_val`. + (raw_field!($root, $parent, $begin) as usize, + raw_field!($root, $parent, $end) as usize + $crate::mem::size_of_val(&(*$root).$end)) + }}; + (@helper $root:ident, $parent:path, # $begin:tt [] .. $end:tt) => {{ + (raw_field!($root, $parent, $begin) as usize, + raw_field!($root, $parent, $end) as usize) + }}; + // No explicit end for range. + (@helper $root:ident, $parent:path, # $begin:tt [] ..) => {{ + // UB due to taking references to uninitialized fields for `size_of_val`. + (raw_field!($root, $parent, $begin) as usize, + $root as usize + $crate::mem::size_of_val(&*$root)) + }}; + (@helper $root:ident, $parent:path, # $begin:tt [] ..=) => {{ + _memoffset__compile_error!( + "Found inclusive range to the end of a struct. Did you mean '..' instead of '..='?") + }}; + // Just one field. + (@helper $root:ident, $parent:path, # $begin:tt []) => {{ + // UB due to taking references to uninitialized fields for `size_of_val`. + (raw_field!($root, $parent, $begin) as usize, + raw_field!($root, $parent, $begin) as usize + $crate::mem::size_of_val(&(*$root).$begin)) + }}; + // Parsing. + (@helper $root:ident, $parent:path, $(# $begin:tt)+ [] $tt:tt $($rest:tt)*) => {{ + span_of!(@helper $root, $parent, $(#$begin)* #$tt [] $($rest)*) + }}; + (@helper $root:ident, $parent:path, [] $tt:tt $($rest:tt)*) => {{ + span_of!(@helper $root, $parent, #$tt [] $($rest)*) + }}; + + // Entry point. + ($sty:path, $($exp:tt)+) => ({ + unsafe { + // Get a base pointer. + _memoffset__let_base_ptr!(root, $sty); + let base = root as usize; + let (begin, end) = span_of!(@helper root, $sty, [] $($exp)*); + begin-base..end-base + } + }); +} + +#[cfg(test)] +mod tests { + // ANDROID: use std instead of core, since we're not building wiht no-std. + use std::mem; + + #[test] + fn span_simple() { + #[repr(C)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + assert_eq!(span_of!(Foo, a), 0..4); + assert_eq!(span_of!(Foo, b), 4..6); + assert_eq!(span_of!(Foo, c), 8..8 + 8); + } + + #[test] + #[cfg_attr(miri, ignore)] // this creates unaligned references + fn span_simple_packed() { + #[repr(C, packed)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + assert_eq!(span_of!(Foo, a), 0..4); + assert_eq!(span_of!(Foo, b), 4..6); + assert_eq!(span_of!(Foo, c), 6..6 + 8); + } + + #[test] + fn span_forms() { + #[repr(C)] + struct Florp { + a: u32, + } + + #[repr(C)] + struct Blarg { + x: u64, + y: [u8; 56], + z: Florp, + egg: [[u8; 4]; 5], + } + + // Love me some brute force + assert_eq!(0..8, span_of!(Blarg, x)); + assert_eq!(64..68, span_of!(Blarg, z)); + assert_eq!(68..mem::size_of::<Blarg>(), span_of!(Blarg, egg)); + + assert_eq!(8..64, span_of!(Blarg, y..z)); + assert_eq!(0..64, span_of!(Blarg, x..=y)); + } + + #[test] + fn ig_test() { + #[repr(C)] + struct Member { + foo: u32, + } + + #[repr(C)] + struct Test { + x: u64, + y: [u8; 56], + z: Member, + egg: [[u8; 4]; 4], + } + + assert_eq!(span_of!(Test, ..x), 0..0); + assert_eq!(span_of!(Test, ..=x), 0..8); + assert_eq!(span_of!(Test, ..y), 0..8); + assert_eq!(span_of!(Test, ..=y), 0..64); + assert_eq!(span_of!(Test, ..z), 0..64); + assert_eq!(span_of!(Test, ..=z), 0..68); + assert_eq!(span_of!(Test, ..egg), 0..68); + assert_eq!(span_of!(Test, ..=egg), 0..84); + assert_eq!(span_of!(Test, ..), 0..mem::size_of::<Test>()); + assert_eq!( + span_of!(Test, x..), + offset_of!(Test, x)..mem::size_of::<Test>() + ); + assert_eq!( + span_of!(Test, y..), + offset_of!(Test, y)..mem::size_of::<Test>() + ); + + assert_eq!( + span_of!(Test, z..), + offset_of!(Test, z)..mem::size_of::<Test>() + ); + assert_eq!( + span_of!(Test, egg..), + offset_of!(Test, egg)..mem::size_of::<Test>() + ); + assert_eq!( + span_of!(Test, x..y), + offset_of!(Test, x)..offset_of!(Test, y) + ); + assert_eq!( + span_of!(Test, x..=y), + offset_of!(Test, x)..offset_of!(Test, y) + mem::size_of::<[u8; 56]>() + ); + } +} |