From 20aee4b83ee6fc65c8d0386f254149d0a1be4739 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Aug 2019 10:24:45 +0200 Subject: [PATCH 1/7] Add container_of! --- src/container_of.rs | 36 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 2 files changed, 38 insertions(+) create mode 100644 src/container_of.rs diff --git a/src/container_of.rs b/src/container_of.rs new file mode 100644 index 0000000..dc718c3 --- /dev/null +++ b/src/container_of.rs @@ -0,0 +1,36 @@ + + +/// 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 = &container.b; +/// let container2: *const Foo = unsafe { container_of!(field, Foo, b) }; +/// assert_eq!(&container as *const Foo, container2); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! container_of { + ($ptr:expr, $container:path, $field:tt) => { + ($ptr as *const _ as *const u8).sub(offset_of!($container, $field)) + as *mut $container + }; +} 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; From a77348bd619af9790b6eee630ba2c476358e673b Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Aug 2019 10:25:40 +0200 Subject: [PATCH 2/7] Change the struct parameter from a tt to a path. This allows name paths with :: to be passed in. --- src/offset_of.rs | 18 ++++++++++++++---- src/span_of.rs | 22 +++++++++++----------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/offset_of.rs b/src/offset_of.rs index b8e0dfe..5e1af94 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,14 @@ 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); From 78c0ec48c86f0ba5b1a6502809c959cf01848923 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Aug 2019 11:05:06 +0200 Subject: [PATCH 3/7] Fix CI --- src/container_of.rs | 4 +--- src/offset_of.rs | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/container_of.rs b/src/container_of.rs index dc718c3..b280558 100644 --- a/src/container_of.rs +++ b/src/container_of.rs @@ -1,5 +1,3 @@ - - /// Calculates the address of a containing struct from a pointer to one of its /// fields. /// @@ -30,7 +28,7 @@ #[macro_export(local_inner_macros)] macro_rules! container_of { ($ptr:expr, $container:path, $field:tt) => { - ($ptr as *const _ as *const u8).sub(offset_of!($container, $field)) + ($ptr as *const _ as *const u8).offset(-(offset_of!($container, $field) as isize)) as *mut $container }; } diff --git a/src/offset_of.rs b/src/offset_of.rs index 5e1af94..0346dce 100644 --- a/src/offset_of.rs +++ b/src/offset_of.rs @@ -138,7 +138,9 @@ mod tests { fn path() { mod sub { #[repr(C)] - pub struct Foo { pub x: u32 } + pub struct Foo { + pub x: u32, + } } assert_eq!(offset_of!(sub::Foo, x), 0); From e6f7863174568ed75be2e8b8486f71f53f56b9d4 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Aug 2019 14:18:03 +0200 Subject: [PATCH 4/7] Check that the pointer in container_of! has the correct type --- src/container_of.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/container_of.rs b/src/container_of.rs index b280558..beee14c 100644 --- a/src/container_of.rs +++ b/src/container_of.rs @@ -27,8 +27,16 @@ /// ``` #[macro_export(local_inner_macros)] macro_rules! container_of { - ($ptr:expr, $container:path, $field:tt) => { - ($ptr as *const _ as *const u8).offset(-(offset_of!($container, $field) as isize)) + ($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 = *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 *mut $container - }; + }}; } From 483d8d97f6656419bc1c2f38f2269d094fcd7857 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Aug 2019 14:23:38 +0200 Subject: [PATCH 5/7] Add tests and change return type to *const --- src/container_of.rs | 68 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/src/container_of.rs b/src/container_of.rs index beee14c..a3ce796 100644 --- a/src/container_of.rs +++ b/src/container_of.rs @@ -20,8 +20,8 @@ /// /// fn main() { /// let container = Foo { a: 1, b: 2, c: [3; 5] }; -/// let field = &container.b; -/// let container2: *const Foo = unsafe { container_of!(field, Foo, b) }; +/// let field_ptr = &container.b; +/// let container2: *const Foo = unsafe { container_of!(field_ptr, Foo, b) }; /// assert_eq!(&container as *const Foo, container2); /// } /// ``` @@ -31,12 +31,70 @@ macro_rules! container_of { let ptr = $ptr as *const _; if false { // Ensure that the pointer has the correct type. - let $container { $field: f, .. }; - f = *ptr; + let $container { $field: _f, .. }; + _f = *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 *mut $container + 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 _); + } + } +} From 89d7948cac8a9737f0fc5c31e9d67f53669bd60f Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Aug 2019 14:24:43 +0200 Subject: [PATCH 6/7] Fix macro requiring Copy fields --- src/container_of.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/container_of.rs b/src/container_of.rs index a3ce796..bae50dc 100644 --- a/src/container_of.rs +++ b/src/container_of.rs @@ -32,7 +32,7 @@ macro_rules! container_of { if false { // Ensure that the pointer has the correct type. let $container { $field: _f, .. }; - _f = *ptr; + _f = $crate::ptr::read(ptr); } // We don't use .sub because we need to support older Rust versions. @@ -97,4 +97,19 @@ mod tests { assert_eq!(container_of!(&x.1, Tup, 1), &x as *const _); } } + + #[test] + fn non_copy() { + use core::cell::Cell; + + #[repr(C)] + struct Foo { + a: Cell, + } + + let x = Foo { a: Cell::new(0) }; + unsafe { + assert_eq!(container_of!(&x.a, Foo, a), &x as *const _); + } + } } From 5ac53418aa324cd54f16e039745b64f40dc4ad0a Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 16 Aug 2019 14:34:38 +0200 Subject: [PATCH 7/7] Use RefCell instead of Cell --- src/container_of.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/container_of.rs b/src/container_of.rs index bae50dc..926edd3 100644 --- a/src/container_of.rs +++ b/src/container_of.rs @@ -100,14 +100,14 @@ mod tests { #[test] fn non_copy() { - use core::cell::Cell; + use core::cell::RefCell; #[repr(C)] struct Foo { - a: Cell, + a: RefCell, } - let x = Foo { a: Cell::new(0) }; + let x = Foo { a: RefCell::new(0) }; unsafe { assert_eq!(container_of!(&x.a, Foo, a), &x as *const _); }