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

Deriving NoUninit (and therefore Pod) without repr(packed) by asserting all fields have the same alignment #263

Open
ds84182 opened this issue Aug 15, 2024 · 0 comments
Labels
proc-macros I don't do proc-macros, but I accepts PRs about them.

Comments

@ds84182
Copy link

ds84182 commented Aug 15, 2024

An asserting const within a trait impl will trigger a late-monomorphization error. This can be used to directly check for uninit bytes.

This should be opt-in in the derive trait. For manual implementations a helper macro could provide the same field checks.

Please note that this implementation is was written in a couple of minutes, so it may have unforeseen issues.

Example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8d97b800a96d2becd5d7b75fa52fb3fe

unsafe trait NoUninit {
    // Asserts fields are aligned, returns alignment
    const FIELDS_ALIGNED: usize;
}

unsafe impl NoUninit for u8 {
    const FIELDS_ALIGNED: usize = std::mem::align_of::<Self>();
}
unsafe impl NoUninit for u16 {
    const FIELDS_ALIGNED: usize = std::mem::align_of::<Self>();
}
unsafe impl<const N: usize, T: NoUninit> NoUninit for [T; N] {
    const FIELDS_ALIGNED: usize = <T as NoUninit>::FIELDS_ALIGNED;
}

#[repr(C)]
struct MaybeNoUninit<T> {
    byte: u8,
    payload: T,
}

unsafe impl<T: NoUninit> NoUninit for MaybeNoUninit<T> {
    const FIELDS_ALIGNED: usize = {
        let mut offset = 0;
        
        let field_align = <u8 as NoUninit>::FIELDS_ALIGNED;
        offset += std::mem::size_of::<u8>();
        offset = (offset + (field_align - 1)) & !(field_align - 1);
        
        let field_align = <T as NoUninit>::FIELDS_ALIGNED;
        assert!((offset & (field_align - 1)) == 0, "`payload` is misaligned, `byte` has a smaller alignment");
        offset += std::mem::size_of::<u8>();
        offset = (offset + (field_align - 1)) & !(field_align - 1);
        
        let struct_align = std::mem::align_of::<T>();
        assert!((offset & (struct_align - 1)) == 0, "struct has trailing padding bytes");
        struct_align
    };
}

fn main() {
    // These are fine:
    let _ = <MaybeNoUninit<u8> as NoUninit>::FIELDS_ALIGNED;
    let _ = <MaybeNoUninit<[u8; 2]> as NoUninit>::FIELDS_ALIGNED;
    let _ = <MaybeNoUninit<[[u8; 2]; 8]> as NoUninit>::FIELDS_ALIGNED;
    
    // Causes late-mono error:
    let _ = <MaybeNoUninit<u16> as NoUninit>::FIELDS_ALIGNED;
}
@Lokathor Lokathor added the proc-macros I don't do proc-macros, but I accepts PRs about them. label Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proc-macros I don't do proc-macros, but I accepts PRs about them.
Projects
None yet
Development

No branches or pull requests

2 participants