Retrieving prefix bytes of types with trailing padding
- Dominant language
- Rust
- Stars
- 2.6k
- Forks
- 179
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 29
Description
Currently, `IntoBytes` cannot be implemented for types with trailing padding because the derived `as_bytes` implementation would expose the uninitialized padding bytes, which is unsound. This means there’s no safe way to access only the initialized prefix bytes of such a type.
For example:
```rs
#[derive(FromBytes, KnownLayout, Immutable)]
#[derive(IntoBytes)] // this should and does fail to compile
#[repr(C)]
struct StructWithPadding {
x: u16,
y: u8,
}
let s = StructWithPadding { x: 1, y: 2 };
assert_eq!(s.as_bytes(), &[1, 0, 2]); // fails, because even if `IntoBytes` was implemented, it would also expose the padding byte
```
## Proposed Solution: Trait for the initialized prefix
A new trait, that works similar to `IntoBytes`, but returns the longest prefix byte slice that does only contain initialized bytes. For types that implement `IntoBytes`, these traits would function identically, but for types, that may have trailing padding and no other padding, it returns what `IntoBytes` would return, but without the (uninitialized) padding.
```rs
pub unsafe trait IntoInitPrefixBytes {
fn as_init_prefix_bytes(&self) -> &[u8]
where Self: Immutable;
}
// # Examples
#[derive(IntoInitPrefixBytes)]
#[repr(C)]
struct StructWithPadding { x: u16, y: u8 }
assert_eq!((StructWithPadding { x: 1, y: 2 }).as_init_prefix_bytes(), &[1, 0, 2]);
// For this type, `self.as_init_prefix_bytes()` would effectively return `&self.0`.
#[derive(FromBytes, KnownLayout, Immutable, SplitAt, IntoInitPrefixBytes)]
#[repr(C, align(4))]
struct AlignedBytes([u8]);
// It is currently impossible to access the memory representation of this struct.
// This struct allows:
// * Well aligned accesses to e.g. `u32`s in the payload with only one bound check.
// * Accessing the payload as a byte slice (for e.g. strings) with a size that doesn't have to be aligned, without requiring any checks.
#[derive(FromBytes, KnownLayout, Immutable, SplitAt, IntoInitPrefixBytes)]
#[repr(C)]
struct NetlinkAttr {
nlmsg_len: u16,
nlmsg_type: u16,
payload: AlignedBytes,
}
let attr_data: [u16; 3] = [6, 1, 2];
let attr: &NetlinkAttr = NetlinkAttr::ref_from_bytes(attr_data.as_bytes()).unwrap();
assert_eq!(&attr.payload.0, &[2, 0]); // accessing as byte slice with only length 2
netlink_socket.send(attr.as_init_prefix_bytes());
```
Open question: What to do with types with non-trailing padding, as here:
```rs
#[derive(FromBytes, IntoInitPrefixBytes)]
#[repr(C)]
struct AnotherStructWithPadding {
a: u8,
b: u16,
c: u8,
}
// should we
// a) return a slice of length 1, because this is the largest init prefix, or
// b) throw a compile error when the trailing bytes are not exclusively padding
assert_eq!(AnotherStructWithPadding::new_zeroed().as_init_prefix_bytes().len(), 1);
```
## Problems, that this would solve
* For socket-`send()`ing types, that have align requirements as well as trailing padding, we need to access the underlying bytes of that type
* As a workaround for working with DSTs that currently fail padding analysis (#1112)
Contributor guide
Assessment
This issue has not been assessed yet.