Skip to content

Commit e1fa7cc

Browse files
authored
Merge pull request #3 from Diggsey/fix-ub
Fix undefined behaviour.
2 parents 543c889 + 952d5b2 commit e1fa7cc

File tree

3 files changed

+95
-31
lines changed

3 files changed

+95
-31
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
[package]
22
name = "field-offset"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
authors = ["Diggory Blake <[email protected]>"]
55
description = "Safe pointer-to-member implementation"
66
repository = "https://github.com/Diggsey/rust-field-offset"
77
readme = "README.md"
88
license = "MIT OR Apache-2.0"
99

1010
[dependencies]
11+
12+
[build-dependencies]
13+
rustc_version = "0.2.3"

build.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
extern crate rustc_version;
2+
use rustc_version::{version, Version};
3+
4+
fn main() {
5+
// Assert we haven't travelled back in time
6+
assert!(version().unwrap().major >= 1);
7+
8+
// Check for a minimum version
9+
if version().unwrap() >= Version::parse("1.36.0").unwrap() {
10+
println!("cargo:rustc-cfg=fieldoffset_maybe_uninit");
11+
}
12+
}

src/lib.rs

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,94 +5,109 @@ use std::ops::Add;
55
use std::fmt;
66

77
/// Represents a pointer to a field of type `U` within the type `T`
8+
#[repr(transparent)]
89
pub struct FieldOffset<T, U>(
910
/// Offset in bytes of the field within the struct
1011
usize,
1112
/// A pointer-to-member can be thought of as a function from
1213
/// `&T` to `&U` with matching lifetimes
13-
PhantomData<for<'a> Fn(&'a T) -> &'a U>
14+
PhantomData<dyn for<'a> Fn(&'a T) -> &'a U>
1415
);
1516

1617
impl<T, U> FieldOffset<T, U> {
18+
// Use MaybeUninit to get a fake T
19+
#[cfg(fieldoffset_maybe_uninit)]
20+
#[inline]
21+
fn with_uninit_ptr<R, F: FnOnce(*const T) -> R>(f: F) -> R {
22+
let uninit = mem::MaybeUninit::<T>::uninit();
23+
f(uninit.as_ptr())
24+
}
25+
26+
// Use a dangling pointer to get a fake T
27+
#[cfg(not(fieldoffset_maybe_uninit))]
28+
#[inline]
29+
fn with_uninit_ptr<R, F: FnOnce(*const T) -> R>(f: F) -> R {
30+
f(mem::align_of::<T>() as *const T)
31+
}
32+
1733
/// Construct a field offset via a lambda which returns a reference
1834
/// to the field in question.
1935
///
2036
/// The lambda *must not* access the value passed in.
21-
pub unsafe fn new<F: for<'a> FnOnce(&'a T) -> &'a U>(f: F) -> Self {
22-
// Construct a "fake" T. It's not valid, but the lambda shouldn't
23-
// actually access it (which is why this is unsafe)
24-
let x = mem::zeroed();
25-
let offset = {
26-
let x = &x;
27-
// Pass a reference to the zeroed T to the lambda
28-
// The lambda gives us back a reference to (what we hope is)
29-
// a field of T, of type U
30-
let y = f(x);
31-
// Compute the offset of the field via the difference between the
32-
// references `x` and `y`. Overflow is an error: in debug builds it
33-
// will be caught here, in release it will wrap around and be caught
34-
// on the next line.
35-
(y as *const U as usize) - (x as *const T as usize)
36-
};
37-
// Don't run destructor on "fake" T
38-
mem::forget(x);
37+
pub unsafe fn new<F: for<'a> FnOnce(*const T) -> *const U>(f: F) -> Self {
38+
let offset = Self::with_uninit_ptr(|base_ptr| {
39+
let field_ptr = f(base_ptr);
40+
(field_ptr as usize).wrapping_sub(base_ptr as usize)
41+
});
42+
43+
// Construct an instance using the offset
44+
Self::new_from_offset(offset)
45+
}
46+
/// Construct a field offset directly from a byte offset.
47+
#[inline]
48+
pub unsafe fn new_from_offset(offset: usize) -> Self {
3949
// Sanity check: ensure that the field offset plus the field size
4050
// is no greater than the size of the containing struct. This is
4151
// not sufficient to make the function *safe*, but it does catch
4252
// obvious errors like returning a reference to a boxed value,
4353
// which is owned by `T` and so has the correct lifetime, but is not
4454
// actually a field.
4555
assert!(offset + mem::size_of::<U>() <= mem::size_of::<T>());
46-
// Construct an instance using the offset
47-
Self::new_from_offset(offset)
48-
}
49-
/// Construct a field offset directly from a byte offset.
50-
pub unsafe fn new_from_offset(offset: usize) -> Self {
56+
5157
FieldOffset(offset, PhantomData)
5258
}
5359
// Methods for applying the pointer to member
5460
/// Apply the field offset to a native pointer.
61+
#[inline]
5562
pub fn apply_ptr<'a>(&self, x: *const T) -> *const U {
5663
((x as usize) + self.0) as *const U
5764
}
5865
/// Apply the field offset to a native mutable pointer.
66+
#[inline]
5967
pub fn apply_ptr_mut<'a>(&self, x: *mut T) -> *mut U {
6068
((x as usize) + self.0) as *mut U
6169
}
6270
/// Apply the field offset to a reference.
71+
#[inline]
6372
pub fn apply<'a>(&self, x: &'a T) -> &'a U {
6473
unsafe { &*self.apply_ptr(x) }
6574
}
6675
/// Apply the field offset to a mutable reference.
76+
#[inline]
6777
pub fn apply_mut<'a>(&self, x: &'a mut T) -> &'a mut U {
6878
unsafe { &mut *self.apply_ptr_mut(x) }
6979
}
7080
/// Get the raw byte offset for this field offset.
81+
#[inline]
7182
pub fn get_byte_offset(&self) -> usize {
7283
self.0
7384
}
7485
// Methods for unapplying the pointer to member
7586
/// Unapply the field offset to a native pointer.
7687
///
7788
/// *Warning: very unsafe!*
89+
#[inline]
7890
pub unsafe fn unapply_ptr<'a>(&self, x: *const U) -> *const T {
7991
((x as usize) - self.0) as *const T
8092
}
8193
/// Unapply the field offset to a native mutable pointer.
8294
///
8395
/// *Warning: very unsafe!*
96+
#[inline]
8497
pub unsafe fn unapply_ptr_mut<'a>(&self, x: *mut U) -> *mut T {
8598
((x as usize) - self.0) as *mut T
8699
}
87100
/// Unapply the field offset to a reference.
88101
///
89102
/// *Warning: very unsafe!*
103+
#[inline]
90104
pub unsafe fn unapply<'a>(&self, x: &'a U) -> &'a T {
91105
&*self.unapply_ptr(x)
92106
}
93107
/// Unapply the field offset to a mutable reference.
94108
///
95109
/// *Warning: very unsafe!*
110+
#[inline]
96111
pub unsafe fn unapply_mut<'a>(&self, x: &'a mut U) -> &'a mut T {
97112
&mut *self.unapply_ptr_mut(x)
98113
}
@@ -107,6 +122,7 @@ impl<T, U> FieldOffset<T, U> {
107122
impl<T, U, V> Add<FieldOffset<U, V>> for FieldOffset<T, U> {
108123
type Output = FieldOffset<T, V>;
109124

125+
#[inline]
110126
fn add(self, other: FieldOffset<U, V>) -> FieldOffset<T, V> {
111127
FieldOffset(self.0 + other.0, PhantomData)
112128
}
@@ -140,12 +156,20 @@ impl<T, U> Clone for FieldOffset<T, U> {
140156
/// `offset_of!(Foo => bar: Bar => x)`
141157
#[macro_export]
142158
macro_rules! offset_of {
143-
($t: path => $f: ident) => {
144-
unsafe { $crate::FieldOffset::<$t, _>::new(|x| {
145-
let $t { ref $f, .. } = *x;
146-
$f
147-
}) }
148-
};
159+
($t: tt => $f: tt) => {{
160+
// Make sure the field exists, and is not being accessed via Deref.
161+
let $t { $f: _, .. };
162+
163+
// Construct the offset
164+
#[allow(unused_unsafe)]
165+
unsafe {
166+
$crate::FieldOffset::<$t, _>::new(|x| {
167+
// This is UB unless/until the compiler special-cases it to
168+
// not enforce the validity constraint on `x`.
169+
&(*x).$f as *const _
170+
})
171+
}
172+
}};
149173
($t: path => $f: ident: $($rest: tt)*) => {
150174
offset_of!($t => $f) + offset_of!($($rest)*)
151175
};
@@ -167,6 +191,9 @@ mod tests {
167191
y: Foo,
168192
}
169193

194+
#[derive(Debug)]
195+
struct Tuple(i32, f64);
196+
170197
#[test]
171198
fn test_simple() {
172199
// Get a pointer to `b` within `Foo`
@@ -193,6 +220,28 @@ mod tests {
193220
assert!(x.b == 42.0);
194221
}
195222

223+
#[test]
224+
fn test_tuple() {
225+
// Get a pointer to `b` within `Foo`
226+
let tuple_1 = offset_of!(Tuple => 1);
227+
228+
// Construct an example `Foo`
229+
let mut x = Tuple(1, 42.0);
230+
231+
// Apply the pointer to get at `b` and read it
232+
{
233+
let y = tuple_1.apply(&x);
234+
assert!(*y == 42.0);
235+
}
236+
237+
// Apply the pointer to get at `b` and mutate it
238+
{
239+
let y = tuple_1.apply_mut(&mut x);
240+
*y = 5.0;
241+
}
242+
assert!(x.1 == 5.0);
243+
}
244+
196245
#[test]
197246
fn test_nested() {
198247
// Construct an example `Foo`

0 commit comments

Comments
 (0)