From 7dc779f328afe24c4f613fb49dfee0c2dd90f4ed Mon Sep 17 00:00:00 2001 From: Andrew Gaspar Date: Fri, 23 Oct 2020 01:16:25 -0600 Subject: [PATCH] Support tuples in offset_of (#48) Support tuple types via new offset_of_tuple / raw_field_tuple macros --- README.md | 1 + build.rs | 3 +++ src/offset_of.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++--- src/raw_field.rs | 31 +++++++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b035674..e73a44b 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,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+) * `span_of!` for obtaining the range that a field, or fields, span. `memoffset` works under `no_std` environments. diff --git a/build.rs b/build.rs index 5749207..c83bff2 100644 --- a/build.rs +++ b/build.rs @@ -4,6 +4,9 @@ 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"); } diff --git a/src/offset_of.rs b/src/offset_of.rs index bb2bce1..2ffb79c 100644 --- a/src/offset_of.rs +++ b/src/offset_of.rs @@ -24,7 +24,7 @@ #[macro_export] #[doc(hidden)] macro_rules! _memoffset__let_base_ptr { - ($name:ident, $type:path) => { + ($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 @@ -38,7 +38,7 @@ macro_rules! _memoffset__let_base_ptr { #[macro_export] #[doc(hidden)] macro_rules! _memoffset__let_base_ptr { - ($name:ident, $type:path) => { + ($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; @@ -66,7 +66,7 @@ macro_rules! _memoffset_offset_from { }; } -/// Calculates the offset of the specified field from the start of the struct. +/// Calculates the offset of the specified field from the start of the named struct. /// /// ## Examples /// ``` @@ -97,6 +97,30 @@ macro_rules! offset_of { }}; } +/// 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] @@ -160,6 +184,19 @@ mod tests { 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)] @@ -180,6 +217,27 @@ mod tests { 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() { diff --git a/src/raw_field.rs b/src/raw_field.rs index 51a4c41..16d01bb 100644 --- a/src/raw_field.rs +++ b/src/raw_field.rs @@ -63,6 +63,16 @@ macro_rules! _memoffset__field_check { }; } +/// 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. /// @@ -82,3 +92,24 @@ macro_rules! raw_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) + } + }}; +}