-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fn unaligned_lvl_slice
: Safely encapsulate "unaligned" slices of `l…
…vl: &[[u8; 4]]`, as previous accesses were UB (#731) See: * #728 (comment) * #728 (comment) The current "unaligned" access of `lvl`, added in #726, and a proposed change in #728, [are UB according to `miri`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=80849f822a890cabd324b3b898b291b8), as they cast `&[u8; 3]` and `&u8` to `&[u8; 4]`, which is an out of bounds read of a borrow. This `fn unaligned_lvl_slice` helper `fn` fixes it and passes `miri` by transmuting (through `.align_to`) to a `&[u8]`, doing the slice, and then doing a checked cast back to `[u8; 4]`. It also does correct bounds checking, as the previous ways were off by one in their bounds checking, as they didn't consider the read of the unaligned part into the next `[u8; 4]`. Doing it this way also optimizes just as well as the previous methods. We could also use a dependency like `zerocopy` to encapsulate the `unsafe` `.align_to`, but as long as these kinds of "unaligned" array reads are a one-off, doing it inline here seems better. Update: Switched to using unstable `fn`s from `std` that make things fully safe and correct, copied into our codebase for stability. I already figured out how to fix the UB in reviewing #728, so I just went ahead and made the fix independently.
- Loading branch information
Showing
3 changed files
with
83 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
//! Unstable `fn`s copied directly from `std`, with the following differences: | ||
//! * They are free `fn`s now, not methods. | ||
//! * `self` is replaced by `this`. | ||
//! * Things only accessible by `std` are replaced with stable counterparts, such as: | ||
//! * `exact_div` => `/` | ||
//! * `.unchecked_mul` => `*` | ||
//! * `const` `.expect` => `match` and `panic!` | ||
|
||
use std::mem; | ||
use std::slice::from_raw_parts; | ||
|
||
/// From `1.75.0`. | ||
pub const fn flatten<const N: usize, T>(this: &[[T; N]]) -> &[T] { | ||
let len = if mem::size_of::<T>() == 0 { | ||
match this.len().checked_mul(N) { | ||
None => panic!("slice len overflow"), | ||
Some(it) => it, | ||
} | ||
} else { | ||
// SAFETY: `this.len() * N` cannot overflow because `self` is | ||
// already in the address space. | ||
/* unsafe */ | ||
this.len() * N | ||
}; | ||
// SAFETY: `[T]` is layout-identical to `[T; N]` | ||
unsafe { from_raw_parts(this.as_ptr().cast(), len) } | ||
} | ||
|
||
/// From `1.75.0`. | ||
#[inline] | ||
#[must_use] | ||
pub const unsafe fn as_chunks_unchecked<const N: usize, T>(this: &[T]) -> &[[T; N]] { | ||
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length | ||
let new_len = /* unsafe */ { | ||
assert!(N != 0 && this.len() % N == 0); | ||
this.len() / N | ||
}; | ||
// SAFETY: We cast a slice of `new_len * N` elements into | ||
// a slice of `new_len` many `N` elements chunks. | ||
unsafe { from_raw_parts(this.as_ptr().cast(), new_len) } | ||
} | ||
|
||
/// From `1.75.0`. | ||
#[inline] | ||
#[track_caller] | ||
#[must_use] | ||
pub const fn as_chunks<const N: usize, T>(this: &[T]) -> (&[[T; N]], &[T]) { | ||
assert!(N != 0, "chunk size must be non-zero"); | ||
let len = this.len() / N; | ||
let (multiple_of_n, remainder) = this.split_at(len * N); | ||
// SAFETY: We already panicked for zero, and ensured by construction | ||
// that the length of the subslice is a multiple of N. | ||
let array_slice = unsafe { as_chunks_unchecked(multiple_of_n) }; | ||
(array_slice, remainder) | ||
} |