3.1 KiB
3.1 KiB
id, original_id, level, impact, clippy
| id | original_id | level | impact | clippy |
|---|---|---|---|---|
| ptr-04 | G.UNS.PTR.01 | G | HIGH | cast_ptr_alignment |
Do Not Dereference Pointers Cast to Misaligned Types
Summary
When casting a pointer to a different type, ensure the resulting pointer is properly aligned for the target type.
Rationale
Misaligned pointer dereferences are undefined behavior on most architectures. Even on architectures that support unaligned access, it may cause performance penalties or subtle bugs.
Bad Example
// DON'T: Cast without checking alignment
fn bad_cast(bytes: &[u8]) -> u32 {
// BAD: bytes might not be aligned for u32
let ptr = bytes.as_ptr() as *const u32;
unsafe { *ptr } // UB if misaligned!
}
// DON'T: Assume struct layout
#[repr(C)]
struct Header {
flags: u8,
value: u32, // Aligned at offset 4 in the struct
}
fn bad_field_access(bytes: &[u8]) -> u32 {
let header = bytes.as_ptr() as *const Header;
// Even if bytes is 4-byte aligned, this might fail
// if Header has different alignment than expected
unsafe { (*header).value }
}
Good Example
// DO: Use read_unaligned for potentially misaligned data
fn good_cast(bytes: &[u8]) -> u32 {
assert!(bytes.len() >= 4);
let ptr = bytes.as_ptr() as *const u32;
// SAFETY: We're reading 4 bytes, alignment doesn't matter for read_unaligned
unsafe { ptr.read_unaligned() }
}
// DO: Check alignment before cast
fn good_aligned_cast(bytes: &[u8]) -> Option<&u32> {
if bytes.len() >= 4 && bytes.as_ptr() as usize % std::mem::align_of::<u32>() == 0 {
// SAFETY: Checked length and alignment
Some(unsafe { &*(bytes.as_ptr() as *const u32) })
} else {
None
}
}
// DO: Use from_ne_bytes for portable byte conversion
fn good_from_bytes(bytes: &[u8]) -> u32 {
u32::from_ne_bytes(bytes[..4].try_into().unwrap())
}
// DO: Use bytemuck for safe transmutation
// use bytemuck::{Pod, Zeroable};
// let value: u32 = bytemuck::pod_read_unaligned(bytes);
// DO: Use align_to for splitting at alignment boundaries
fn process_aligned(bytes: &[u8]) {
let (prefix, aligned, suffix) = unsafe { bytes.align_to::<u32>() };
// prefix and suffix are unaligned portions
// aligned is a &[u32] that's properly aligned
}
Alignment Check Helpers
fn is_aligned<T>(ptr: *const u8) -> bool {
ptr as usize % std::mem::align_of::<T>() == 0
}
/// Align a pointer up to the next aligned address
fn align_up<T>(ptr: *const u8) -> *const u8 {
let align = std::mem::align_of::<T>();
let addr = ptr as usize;
let aligned = (addr + align - 1) & !(align - 1);
aligned as *const u8
}
Architecture Notes
| Arch | Misaligned Access |
|---|---|
| x86/x64 | Works but slower |
| ARM | UB, may trap or give wrong results |
| RISC-V | UB, may trap |
| WASM | UB |
Checklist
- Is my pointer cast changing alignment requirements?
- Is the source pointer guaranteed to be aligned?
- Should I use read_unaligned instead?
- Can I use safe conversion methods (from_ne_bytes)?
Related Rules
mem-01: Choose appropriate data layoutffi-13: Ensure consistent data layout