diff --git a/src/container_of.rs b/src/container_of.rs new file mode 100644 index 0000000..926edd3 --- /dev/null +++ b/src/container_of.rs @@ -0,0 +1,115 @@ +/// Calculates the address of a containing struct from a pointer to one of its +/// fields. +/// +/// # Safety +/// +/// This is unsafe because it assumes that the given expression is a valid +/// pointer to the specified field of some container type. +/// +/// ## Examples +/// ``` +/// #[macro_use] +/// extern crate memoffset; +/// +/// #[repr(C, packed)] +/// struct Foo { +/// a: u32, +/// b: u64, +/// c: [u8; 5] +/// } +/// +/// fn main() { +/// let container = Foo { a: 1, b: 2, c: [3; 5] }; +/// let field_ptr = &container.b; +/// let container2: *const Foo = unsafe { container_of!(field_ptr, Foo, b) }; +/// assert_eq!(&container as *const Foo, container2); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! container_of { + ($ptr:expr, $container:path, $field:tt) => {{ + let ptr = $ptr as *const _; + if false { + // Ensure that the pointer has the correct type. + let $container { $field: _f, .. }; + _f = $crate::ptr::read(ptr); + } + + // We don't use .sub because we need to support older Rust versions. + (ptr as *const u8).offset((offset_of!($container, $field) as isize).wrapping_neg()) + as *const $container + }}; +} + +#[cfg(test)] +mod tests { + #[test] + fn simple() { + #[repr(C)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + let x = Foo { + a: 0, + b: [0; 2], + c: 0, + }; + unsafe { + assert_eq!(container_of!(&x.a, Foo, a), &x as *const _); + assert_eq!(container_of!(&x.b, Foo, b), &x as *const _); + assert_eq!(container_of!(&x.c, Foo, c), &x as *const _); + } + } + + #[test] + #[cfg(not(miri))] // this creates unaligned references + fn simple_packed() { + #[repr(C, packed)] + struct Foo { + a: u32, + b: [u8; 2], + c: i64, + } + + let x = Foo { + a: 0, + b: [0; 2], + c: 0, + }; + unsafe { + assert_eq!(container_of!(&x.a, Foo, a), &x as *const _); + assert_eq!(container_of!(&x.b, Foo, b), &x as *const _); + assert_eq!(container_of!(&x.c, Foo, c), &x as *const _); + } + } + + #[test] + fn tuple_struct() { + #[repr(C)] + struct Tup(i32, i32); + + let x = Tup(0, 0); + unsafe { + assert_eq!(container_of!(&x.0, Tup, 0), &x as *const _); + assert_eq!(container_of!(&x.1, Tup, 1), &x as *const _); + } + } + + #[test] + fn non_copy() { + use core::cell::RefCell; + + #[repr(C)] + struct Foo { + a: RefCell, + } + + let x = Foo { a: RefCell::new(0) }; + unsafe { + assert_eq!(container_of!(&x.a, Foo, a), &x as *const _); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4a97ee6..da472f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,3 +83,5 @@ pub use core::ptr; mod offset_of; #[macro_use] mod span_of; +#[macro_use] +mod container_of; diff --git a/src/offset_of.rs b/src/offset_of.rs index b8e0dfe..0346dce 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:tt) => { + ($name:ident, $type:path) => { // 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:tt) => { + ($name:ident, $type:path) => { // No UB right here, but we will later dereference this pointer to // offset into a field, and that is UB when the pointer is dangling. let $name = $crate::mem::align_of::<$type>() as *const $type; @@ -49,7 +49,7 @@ macro_rules! _memoffset__let_base_ptr { #[macro_export] #[doc(hidden)] macro_rules! _memoffset__field_check { - ($type:tt, $field:tt) => { + ($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. @@ -78,7 +78,7 @@ macro_rules! _memoffset__field_check { /// ``` #[macro_export(local_inner_macros)] macro_rules! offset_of { - ($parent:tt, $field:tt) => {{ + ($parent:path, $field:tt) => {{ _memoffset__field_check!($parent, $field); // Get a base pointer. @@ -133,4 +133,16 @@ mod tests { 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); + } } diff --git a/src/span_of.rs b/src/span_of.rs index c48a456..a968f89 100644 --- a/src/span_of.rs +++ b/src/span_of.rs @@ -96,58 +96,58 @@ macro_rules! span_of { // Lots of UB due to taking references to uninitialized fields! But that can currently // not be avoided. // No explicit begin for range. - (@helper $root:ident, $parent:tt, [] ..) => {{ + (@helper $root:ident, $parent:path, [] ..) => {{ ($root as usize, $root as usize + $crate::mem::size_of_val(&(*$root))) }}; - (@helper $root:ident, $parent:tt, [] ..= $field:tt) => {{ + (@helper $root:ident, $parent:path, [] ..= $field:tt) => {{ _memoffset__field_check!($parent, $field); ($root as usize, &(*$root).$field as *const _ as usize + $crate::mem::size_of_val(&(*$root).$field)) }}; - (@helper $root:ident, $parent:tt, [] .. $field:tt) => {{ + (@helper $root:ident, $parent:path, [] .. $field:tt) => {{ _memoffset__field_check!($parent, $field); ($root as usize, &(*$root).$field as *const _ as usize) }}; // Explicit begin and end for range. - (@helper $root:ident, $parent:tt, # $begin:tt [] ..= $end:tt) => {{ + (@helper $root:ident, $parent:path, # $begin:tt [] ..= $end:tt) => {{ _memoffset__field_check!($parent, $begin); _memoffset__field_check!($parent, $end); (&(*$root).$begin as *const _ as usize, &(*$root).$end as *const _ as usize + $crate::mem::size_of_val(&(*$root).$end)) }}; - (@helper $root:ident, $parent:tt, # $begin:tt [] .. $end:tt) => {{ + (@helper $root:ident, $parent:path, # $begin:tt [] .. $end:tt) => {{ _memoffset__field_check!($parent, $begin); _memoffset__field_check!($parent, $end); (&(*$root).$begin as *const _ as usize, &(*$root).$end as *const _ as usize) }}; // No explicit end for range. - (@helper $root:ident, $parent:tt, # $begin:tt [] ..) => {{ + (@helper $root:ident, $parent:path, # $begin:tt [] ..) => {{ _memoffset__field_check!($parent, $begin); (&(*$root).$begin as *const _ as usize, $root as usize + $crate::mem::size_of_val(&*$root)) }}; - (@helper $root:ident, $parent:tt, # $begin:tt [] ..=) => {{ + (@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:tt, # $begin:tt []) => {{ + (@helper $root:ident, $parent:path, # $begin:tt []) => {{ _memoffset__field_check!($parent, $begin); (&(*$root).$begin as *const _ as usize, &(*$root).$begin as *const _ as usize + $crate::mem::size_of_val(&(*$root).$begin)) }}; // Parsing. - (@helper $root:ident, $parent:tt, $(# $begin:tt)+ [] $tt:tt $($rest:tt)*) => {{ + (@helper $root:ident, $parent:path, $(# $begin:tt)+ [] $tt:tt $($rest:tt)*) => {{ span_of!(@helper $root, $parent, $(#$begin)* #$tt [] $($rest)*) }}; - (@helper $root:ident, $parent:tt, [] $tt:tt $($rest:tt)*) => {{ + (@helper $root:ident, $parent:path, [] $tt:tt $($rest:tt)*) => {{ span_of!(@helper $root, $parent, #$tt [] $($rest)*) }}; // Entry point. - ($sty:tt, $($exp:tt)+) => ({ + ($sty:path, $($exp:tt)+) => ({ unsafe { // Get a base pointer. _memoffset__let_base_ptr!(root, $sty);