I'd like to propose adding a new offset_of_val! macro, which is exactly like offset_of! except that the type argument is replaced by a value, and it operates on the type of said value.
That is to say, if we had a typeof operator in Rust, it would be equivalent to this:
macro_rules! offset_of_val {
($value:expr, $($fields:expr)+ $(,)?) => {
offset_of!(typeof($value), $($fields)*)
};
}
Problem statement
We discovered that the kernel's dma_read! macro is unsound. The macro let's you write code like this:
#[repr(C)]
#[derive(IntoBytes, FromBytes)]
struct MyStruct {
field_1: u32,
}
// Dma<MyStruct> is basically an array of MyStruct values
// in volatile memory
let dma_alloc: Dma<MyStruct> = ...;
let x = dma_read!(dma_alloc[7].field_1);
and the macro then expands into essentially a volatile read of DMA memory. The normal api of Dma only lets you read the entire MyStruct value in one big read, but we might only want to read a single field in it. For this purpose we use a macro that figures out what offset (and size) [7].field_1 corresponds in the MyStruct array.
The macro is defined like this:
macro_rules! dma_read {
($dma:expr, $idx: expr, $($field:tt)*) => {{
(|| -> ::core::result::Result<_, $crate::error::Error> {
let item = $crate::dma::CoherentAllocation::item_from_index(&$dma, $idx)?;
// SAFETY: `item_from_index` ensures that `item` is always a valid pointer and can be
// dereferenced. The compiler also further validates the expression on whether `field`
// is a member of `item` when expanded by the macro.
unsafe {
let ptr_field = ::core::ptr::addr_of!((*item) $($field)*);
::core::result::Result::Ok(
$crate::dma::CoherentAllocation::field_read(&$dma, ptr_field)
)
}
})()
}};
($dma:ident [ $idx:expr ] $($field:tt)* ) => {
$crate::dma_read!($dma, $idx, $($field)*)
};
($($dma:ident).* [ $idx:expr ] $($field:tt)* ) => {
$crate::dma_read!($($dma).*, $idx, $($field)*)
};
}
The problem is that the expansion contains the expression addr_of!((*item).field_1). This is normally well-defined, but the user could have defined MyStruct maliciously:
#[repr(C)]
#[derive(IntoBytes, FromBytes)]
struct MyStruct {
field_2: u32,
}
struct SomeOtherType {
field_1: u32,
}
impl Deref for MyStruct {
type Target = SomeOtherType;
fn deref(&self) -> &SomeOtherType {
todo!()
}
}
with this type, using dma_read! with Dma<MyStruct> results in UB because addr_of!((*item).field_1) notices that although MyStruct does not have a field called field_1, it derefs to a type that does. Therefore, it's equivalent to this:
- Create a
&MyStructfrom the*itemexpression. (dereferencing a raw pointer) - Call
MyStruct::deref() - Invoke
addr_of!to get a raw pointer tofield_1of the thing returned byderef().
And this code already invokes UB in step 1 because creating a reference to volatile memory is not legal. Note that step 1 is an unsafe operation because it's a deref of a raw pointer, but it's inside of addr_of! which also requires an unsafe block, so this passes.
Now, one way to solve the above problem is to redefine dma_read! so you call it as dma_read!(MyStruct, dma_alloc[7].field_1). This allows you to replace addr_of! with offset_of!, which triggers a compilation error if deref is involved. Unfortunately, it's quite inconvenient to specify the type every time you invoke dma_read!.
Deref ambiguity hack
Technically there is a way to work around the above. You can use this trick to verify that the type does not implement Deref. This results in a pretty complex macro, and probably hurts compile times because of a bunch of weird logic just to check for this, but it does work. (Note you have to be careful if you want to support a.field.field because not only do you have to check typeof(a): !Deref, but also typeof(a.field): !Deref.)
How offset_of_val! solves this
By emitting an invocation of offset_of_val!(*item, field_1), the compiler can compute the offset for us, and the compiler can be implemented in a way such that deref is not a problem (like it already does for the usual `offset_of!).
Other problems solved by this
The macro could be used by crates that wish to perform an addr_of! operation using pointer::wrapping_add semantics instead of pointer::add semantics.
Type of first argument
I propose that the first argument should be a place expression, and that it should operate on the type of that place. So to operate on field_1 of an *mut Foo, you need offset_of_val!(*ptr, field_1). I think this is the most general way to define it without needing to create a reference.
Naming
This naming is analogous to other methods also defined in core::mem.
size_of/size_of_valalign_of/align_of_val
Alice