Skip to content

Commit 5f8f5a6

Browse files
authored
Support tuples in offset_of (Gilnaa#48)
Support tuple types via new offset_of_tuple / raw_field_tuple macros
1 parent f2a5508 commit 5f8f5a6

File tree

4 files changed

+96
-3
lines changed

4 files changed

+96
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ C-Like `offset_of` functionality for Rust structs.
66

77
Introduces the following macros:
88
* `offset_of!` for obtaining the offset of a member of a struct.
9+
* `offset_of_tuple!` for obtaining the offset of a member of a tuple. (Requires Rust 1.20+)
910
* `span_of!` for obtaining the range that a field, or fields, span.
1011

1112
`memoffset` works under `no_std` environments.

build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ fn main() {
44
let ac = autocfg::new();
55

66
// Check for a minimum version for a few features
7+
if ac.probe_rustc_version(1, 20) {
8+
println!("cargo:rustc-cfg=tuple_ty");
9+
}
710
if ac.probe_rustc_version(1, 31) {
811
println!("cargo:rustc-cfg=allow_clippy");
912
}

src/offset_of.rs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#[macro_export]
2525
#[doc(hidden)]
2626
macro_rules! _memoffset__let_base_ptr {
27-
($name:ident, $type:path) => {
27+
($name:ident, $type:ty) => {
2828
// No UB here, and the pointer does not dangle, either.
2929
// But we have to make sure that `uninit` lives long enough,
3030
// so it has to be in the same scope as `$name`. That's why
@@ -38,7 +38,7 @@ macro_rules! _memoffset__let_base_ptr {
3838
#[macro_export]
3939
#[doc(hidden)]
4040
macro_rules! _memoffset__let_base_ptr {
41-
($name:ident, $type:path) => {
41+
($name:ident, $type:ty) => {
4242
// No UB right here, but we will later dereference this pointer to
4343
// offset into a field, and that is UB because the pointer is dangling.
4444
let $name = $crate::mem::align_of::<$type>() as *const $type;
@@ -66,7 +66,7 @@ macro_rules! _memoffset_offset_from {
6666
};
6767
}
6868

69-
/// Calculates the offset of the specified field from the start of the struct.
69+
/// Calculates the offset of the specified field from the start of the named struct.
7070
///
7171
/// ## Examples
7272
/// ```
@@ -97,6 +97,30 @@ macro_rules! offset_of {
9797
}};
9898
}
9999

100+
/// Calculates the offset of the specified field from the start of the tuple.
101+
///
102+
/// ## Examples
103+
/// ```
104+
/// #[macro_use]
105+
/// extern crate memoffset;
106+
///
107+
/// fn main() {
108+
/// assert!(offset_of_tuple!((u8, u32), 1) >= 0, "Tuples do not have a defined layout");
109+
/// }
110+
/// ```
111+
#[cfg(tuple_ty)]
112+
#[macro_export(local_inner_macros)]
113+
macro_rules! offset_of_tuple {
114+
($parent:ty, $field:tt) => {{
115+
// Get a base pointer (non-dangling if rustc supports `MaybeUninit`).
116+
_memoffset__let_base_ptr!(base_ptr, $parent);
117+
// Get field pointer.
118+
let field_ptr = raw_field_tuple!(base_ptr, $parent, $field);
119+
// Compute offset.
120+
_memoffset_offset_from!(field_ptr, base_ptr)
121+
}};
122+
}
123+
100124
#[cfg(test)]
101125
mod tests {
102126
#[test]
@@ -160,6 +184,19 @@ mod tests {
160184
assert_eq!(foo(Pair(0, 0)), 4);
161185
}
162186

187+
#[cfg(tuple_ty)]
188+
#[test]
189+
fn test_tuple_offset() {
190+
let f = (0i32, 0.0f32, 0u8);
191+
let f_ptr = &f as *const _;
192+
let f1_ptr = &f.1 as *const _;
193+
194+
assert_eq!(
195+
f1_ptr as usize - f_ptr as usize,
196+
offset_of_tuple!((i32, f32, u8), 1)
197+
);
198+
}
199+
163200
#[test]
164201
fn test_raw_field() {
165202
#[repr(C)]
@@ -180,6 +217,27 @@ mod tests {
180217
assert_eq!(f_ptr as usize + 8, raw_field!(f_ptr, Foo, c) as usize);
181218
}
182219

220+
#[cfg(tuple_ty)]
221+
#[test]
222+
fn test_raw_field_tuple() {
223+
let t = (0u32, 0u8, false);
224+
let t_ptr = &t as *const _;
225+
let t_addr = t_ptr as usize;
226+
227+
assert_eq!(
228+
&t.0 as *const _ as usize - t_addr,
229+
raw_field_tuple!(t_ptr, (u32, u8, bool), 0) as usize - t_addr
230+
);
231+
assert_eq!(
232+
&t.1 as *const _ as usize - t_addr,
233+
raw_field_tuple!(t_ptr, (u32, u8, bool), 1) as usize - t_addr
234+
);
235+
assert_eq!(
236+
&t.2 as *const _ as usize - t_addr,
237+
raw_field_tuple!(t_ptr, (u32, u8, bool), 2) as usize - t_addr
238+
);
239+
}
240+
183241
#[cfg(feature = "unstable_const")]
184242
#[test]
185243
fn const_offset() {

src/raw_field.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ macro_rules! _memoffset__field_check {
6363
};
6464
}
6565

66+
/// Deref-coercion protection macro.
67+
#[macro_export]
68+
#[doc(hidden)]
69+
macro_rules! _memoffset__field_check_tuple {
70+
($type:ty, $field:tt) => {
71+
// Make sure the type argument is a tuple
72+
let (_, ..): $type;
73+
};
74+
}
75+
6676
/// Computes a const raw pointer to the given field of the given base pointer
6777
/// to the given parent type.
6878
///
@@ -82,3 +92,24 @@ macro_rules! raw_field {
8292
}
8393
}};
8494
}
95+
96+
/// Computes a const raw pointer to the given field of the given base pointer
97+
/// to the given parent tuple typle.
98+
///
99+
/// The `base` pointer *must not* be dangling, but it *may* point to
100+
/// uninitialized memory.
101+
#[cfg(tuple_ty)]
102+
#[macro_export(local_inner_macros)]
103+
macro_rules! raw_field_tuple {
104+
($base:expr, $parent:ty, $field:tt) => {{
105+
_memoffset__field_check_tuple!($parent, $field);
106+
107+
// Get the field address.
108+
// Crucially, we know that this will not trigger a deref coercion because
109+
// of the field check we did above.
110+
#[allow(unused_unsafe)] // for when the macro is used in an unsafe block
111+
unsafe {
112+
_memoffset__raw_const!((*($base as *const $parent)).$field)
113+
}
114+
}};
115+
}

0 commit comments

Comments
 (0)