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

Provide an easier-to-use rand API #1431

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 50 additions & 12 deletions src/rand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ where

pub(crate) mod sealed {
use crate::error;
use core::{mem, slice};

pub trait SecureRandom: core::fmt::Debug {
/// Fills `dest` with random bytes.
Expand All @@ -83,21 +84,58 @@ pub(crate) mod sealed {
fn as_mut_bytes(&mut self) -> &mut [u8]; // `AsMut<[u8]>::as_mut`
}

macro_rules! impl_random_arrays {
[ $($len:expr)+ ] => {
$(
impl RandomlyConstructable for [u8; $len] {
#[inline]
fn zero() -> Self { [0; $len] }

#[inline]
fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self[..] }
}
)+
// Safety: in order for a type to be POD, it must satisfy the following criteria:
// - The type must be inhabited (ie not the never type)
// - All bit patterns must be valid
// - The type must have no padding
// - Any fields within the type must also be Pod
pub unsafe trait Pod: Sized {
#[inline]
fn zero() -> Self {
// Safe by the safety guarantees on the trait, notably that all bit patterns are valid
unsafe { mem::zeroed() }
}
}

impl_random_arrays![4 8 16 32 48 64 128 256];
macro_rules! impl_pod {
($($type:ty),*) => {
$(
unsafe impl Pod for $type {}
)*
};
}

impl_pod!(i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize);

impl<T: Pod + Copy, const N: usize> RandomlyConstructable for [T; N] {
#[inline]
fn zero() -> Self {
// Note that the Copy bound on T is required in order to initialize this array
// without unsafe code.
[T::zero(); N]
}

#[inline]
fn as_mut_bytes(&mut self) -> &mut [u8] {
let data = self.as_mut_ptr();
let len = N * mem::size_of::<T>();

// Safety:
// - `data` is valid (aligned, non-null, non-dangling) for reads and writes of size
// `len` bytes
// - The entire memory range of this slice is in a single allocated object because
// it came from another mutable slice
// - `data` is not dangling and properly aligned for all types because, again, it was
// derived from another mutable slice
// - Because T is POD, and because `len` is calculated based on the size of T,
// `data` points to `len` initialized u8s
// - We co-opt the lifetime of `self`, so no borrow checker rules are broken
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this:

        // - We co-opt the lifetime of `self`, so no borrow checker rules are broken

It's hard for me to remember the exact circumstances, but I recall trying to do something similar and finding that I was running into a conflict with the mutable noalias optimization or similar.

This, in conjunction with wanting to avoid the need to initially zero all the bytes to start with, made me want to rewrite this whole module in terms of MaybeUninit and pointers.

WDYT?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I had not heard of the mutable noalias optimization before, and quick google searches of it suggest that it's actually buggy in the LLVM. In a world where there is no mis-compilation, the function signature should essentially be equivalent to fn as_mut_bytes<'a>(&'a mut self) -> &'a mut [u8], and since 'a will be inferred by the usage of the return value, it will extend the borrow of self accordingly. I can ask some other rustaceans I know about it to double check though.

When reading through the module I also thought about what it would look like to switch it over to using raw pointers. I think that's a viable option (and skips the pain of creating an initially zeroed-out array), but I didn't want to get waist deep with changing SecureRandom on my first contribution.

// - `len` is less than `isize::MAX` since it was derived from the size of `self`
unsafe {
slice::from_raw_parts_mut(data as *mut u8, len)
}
}
}
}

/// A type that can be returned by `ring::rand::generate()`.
Expand Down
25 changes: 25 additions & 0 deletions tests/rand_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ fn test_system_random_lengths() {
}
}

#[test]
fn test_randomly_constructable() {
// Picked arbitrarily
const BY: u8 = 0x5A;

let rng = test::rand::FixedByteRandom {
byte: BY
};

assert!(rand::generate::<[u8; 0]>(&rng).is_ok());
assert!(rand::generate::<[u64; 0]>(&rng).is_ok());
assert!(rand::generate::<[usize; 0]>(&rng).is_ok());

let arr: [u8; 256] = rand::generate(&rng)
.unwrap()
.expose();
assert!(arr.iter().all(|&x| x == BY));

let arr: [u32; 256] = rand::generate(&rng)
.unwrap()
.expose();
// Endianness doesn't matter since all the bytes are the same
assert!(arr.iter().all(|&x| x.to_le_bytes() == [BY; 4]));
}

#[test]
fn test_system_random_traits() {
test::compile_time_assert_clone::<rand::SystemRandom>();
Expand Down