Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add container_of #21

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions src/container_of.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
}

let x = Foo { a: RefCell::new(0) };
unsafe {
assert_eq!(container_of!(&x.a, Foo, a), &x as *const _);
}
}
}
Amanieu marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,5 @@ pub use core::ptr;
mod offset_of;
#[macro_use]
mod span_of;
#[macro_use]
mod container_of;
20 changes: 16 additions & 4 deletions src/offset_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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) => {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
// Make sure the field actually exists. This line ensures that a
// compile-time error is generated if $field is accessed through a
// Deref impl.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
}
22 changes: 11 additions & 11 deletions src/span_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down