blob: 07d7db5801d90666e1e2b1d9b3b91610a079ae01 [file]
use super::utility::choose_value;
use super::utility::mutate_integer;
use super::utility::shrink_towards;
use super::Domain;
use anyhow;
use rand::RngExt;
/// Generates arbitrary values of type `T`.
///
/// This domain is used to generate values of a specific type without any constraints
/// beyond the type's intrinsic range. For example, `Arbitrary<i32>` generates `i32`
/// values spanning the whole `i32` domain `[i32::MIN, i32::MAX]`.
///
/// Example usage:
/// ```
/// # use fuzztest::domains::Domain;
/// # use fuzztest::domains::arbitrary::Arbitrary;
/// # use rand::rngs::SmallRng;
/// # use rand::SeedableRng;
///
/// let arbitrary_i32 = Arbitrary::<i32>::default();
/// let mut rng = SmallRng::seed_from_u64(73);
///
/// let sample = arbitrary_i32.init(&mut rng);
/// assert!(sample.is_ok());
/// ```
pub struct Arbitrary<T> {
_phantom: std::marker::PhantomData<T>,
}
// We cannot just use `#[derive(Default)]` because `T` might not be `Default`.
impl<T> Default for Arbitrary<T> {
fn default() -> Self {
Self { _phantom: std::marker::PhantomData }
}
}
impl<T> Arbitrary<T> {
/// Creates a new `Arbitrary` domain for the given type `T`.
pub fn new() -> Self {
Self { _phantom: std::marker::PhantomData }
}
}
impl Domain for Arbitrary<bool> {
type UserValue<'user> = bool;
type CorpusValue = bool;
fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
Ok(rng.random())
}
fn mutate(
&self,
val: &mut Self::CorpusValue,
rng: &mut dyn rand::Rng,
only_shrink: bool,
) -> anyhow::Result<()> {
if only_shrink {
// Convention taken from the C++ fuzztest library.
*val = false;
} else {
*val = rng.random();
}
Ok(())
}
fn get_user_value<'a>(
&self,
corpus_value: &'a Self::CorpusValue,
) -> anyhow::Result<Self::UserValue<'a>> {
Ok(*corpus_value)
}
}
macro_rules! impl_domain_for_integer {
// We need an additional "integer type" for this, because
// rand::distr::Distribution<> is not implemented for StandardUniform for isize and usize.
// so we perform the generation using integer types and cast to the size types if needed.
($ty:ty, $int_ty:ty) => {
impl Domain for Arbitrary<$ty> {
type UserValue<'user> = $ty;
type CorpusValue = $ty;
fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
// We generate a the equivalent integer type so this works for size types.
let val: $int_ty = choose_value(rng);
Ok(val as $ty)
}
fn mutate(
&self,
val: &mut Self::CorpusValue,
rng: &mut dyn rand::Rng,
only_shrink: bool,
) -> anyhow::Result<()> {
if only_shrink {
*val = shrink_towards(rng, *val as $int_ty, 0) as $ty;
} else {
let original_val = *val;
loop {
*val = mutate_integer(rng, *val as $int_ty, 5, None, None) as $ty;
if *val != original_val {
break;
}
}
}
Ok(())
}
fn get_user_value<'a>(
&self,
corpus_value: &'a Self::CorpusValue,
) -> anyhow::Result<Self::UserValue<'a>> {
Ok(*corpus_value)
}
}
};
}
impl_domain_for_integer!(i8, i8);
impl_domain_for_integer!(u8, u8);
impl_domain_for_integer!(i16, i16);
impl_domain_for_integer!(u16, u16);
impl_domain_for_integer!(i32, i32);
impl_domain_for_integer!(u32, u32);
impl_domain_for_integer!(i64, i64);
impl_domain_for_integer!(u64, u64);
impl_domain_for_integer!(i128, i128);
impl_domain_for_integer!(u128, u128);
impl_domain_for_integer!(isize, i64);
impl_domain_for_integer!(usize, u64);
macro_rules! impl_domain_for_float {
($ty:ty) => {
impl Domain for Arbitrary<$ty> {
type UserValue<'user> = $ty;
type CorpusValue = $ty;
fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
Ok(choose_value(rng))
}
fn mutate(
&self,
val: &mut Self::CorpusValue,
rng: &mut dyn rand::Rng,
only_shrink: bool,
) -> anyhow::Result<()> {
if only_shrink {
// Shrink only finite, non-terminal values.
if val.is_finite() && *val != 0.0 {
*val = shrink_towards(rng, *val, 0.0);
}
return Ok(());
}
// Non-shrinking mutation.
let original_val = *val;
loop {
// Loop to ensure mutation
if !val.is_finite() {
// NaN or Infinity
*val = self.init(rng)?;
} else {
match rng.random_range(0..4) {
0 => *val /= 2.0,
1 => *val = -*val,
2 => *val += 1.0,
3 => *val *= 3.0,
_ => unreachable!(),
}
}
if (val.is_nan() && original_val.is_nan()) || *val == original_val {
continue;
}
break;
}
Ok(())
}
fn get_user_value<'a>(
&self,
corpus_value: &'a Self::CorpusValue,
) -> anyhow::Result<Self::UserValue<'a>> {
Ok(*corpus_value)
}
}
};
}
impl_domain_for_float!(f32);
impl_domain_for_float!(f64);
const SURROGATE_START: u32 = 0xD800;
const SURROGATE_END: u32 = 0xDFFF;
const MAX_VALID_CODEPOINT: u32 = 0x10FFFF;
const NUM_VALID_CODEPOINTS: u32 = MAX_VALID_CODEPOINT - (SURROGATE_END - SURROGATE_START);
/// Maps a `char` to a `u32` in a contiguous range.
///
/// Rust's `char` type represents a Unicode Scalar Value, which means it cannot be a
/// surrogate code point (U+D800 to U+DFFF). This function maps `char` values to a
/// contiguous integer range by shifting down the code points that are above the
/// surrogate range. This is useful for mutation strategies like random walks over
/// a dense integer space.
fn map_char_to_int(c: char) -> u32 {
let val = c as u32;
if val >= SURROGATE_START {
val - (SURROGATE_END - SURROGATE_START + 1)
} else {
val
}
}
/// Maps a `u32` from the contiguous range back to a `char`.
///
/// This is the inverse of `map_char_to_int`. It converts an integer from the
/// dense range back to a `char`. Integers that fall within the range previously
/// occupied by surrogates are shifted up to their correct code points.
fn map_int_to_char(u: u32) -> char {
assert!(
u < NUM_VALID_CODEPOINTS,
"input to map_int_to_char out of range: got {}, expected value < {}",
u,
NUM_VALID_CODEPOINTS
);
let val = if u >= SURROGATE_START { u + (SURROGATE_END - SURROGATE_START + 1) } else { u };
std::char::from_u32(val).unwrap()
}
impl Domain for Arbitrary<char> {
type UserValue<'user> = char;
type CorpusValue = char;
fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
Ok(choose_value(rng))
}
fn mutate(
&self,
val: &mut Self::CorpusValue,
rng: &mut dyn rand::Rng,
only_shrink: bool,
) -> anyhow::Result<()> {
if only_shrink {
// Shrink towards the null character (0 as char).
*val = shrink_towards(rng, *val, 0 as char);
} else {
let original_val = *val;
loop {
// Map the current char to a u32 in a contiguous range.
let mapped_val = map_char_to_int(*val);
// Mutate the u32 value within the valid range of mapped code points.
let mutated_mapped_val =
mutate_integer(rng, mapped_val, 5, Some(0), Some(NUM_VALID_CODEPOINTS - 1));
// Map the mutated u32 back to a char.
*val = map_int_to_char(mutated_mapped_val);
if *val != original_val {
break;
}
}
}
Ok(())
}
fn get_user_value<'a>(
&self,
corpus_value: &'a Self::CorpusValue,
) -> anyhow::Result<Self::UserValue<'a>> {
Ok(*corpus_value)
}
}
impl Domain for Arbitrary<()> {
type UserValue<'user> = ();
type CorpusValue = ();
fn init(&self, _rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> {
Ok(())
}
fn mutate(
&self,
_val: &mut Self::CorpusValue,
_rng: &mut dyn rand::Rng,
_only_shrink: bool,
) -> anyhow::Result<()> {
// No possible mutation for the unit type
Ok(())
}
fn get_user_value<'a>(
&self,
_corpus_value: &'a Self::CorpusValue,
) -> anyhow::Result<Self::UserValue<'a>> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use crate::domains::utility::SpecialValues;
use num_traits::{Float, One, Zero};
use rand::distr::uniform::SampleUniform;
use rand::distr::{Distribution, StandardUniform};
use rand::rngs::{SmallRng, SysRng};
use rand::SeedableRng;
use crate::domains::Domain;
use super::*;
type CorpusValueForArbitrary<T> = <Arbitrary<T> as Domain>::CorpusValue;
fn get_rng() -> SmallRng {
SmallRng::try_from_rng(&mut SysRng).unwrap()
}
/// Trait to be able to treat numerical types uniformly w.r.t. finiteness and shrinking target.
trait NumTraitsExtended {
fn is_finite_value(&self) -> bool;
fn is_at_shrink_target(&self) -> bool;
}
macro_rules! impl_num_traits_extended_for_floats {
($($ty:ty),*) => {
$(
impl NumTraitsExtended for $ty {
fn is_finite_value(&self) -> bool {
self.is_finite()
}
fn is_at_shrink_target(&self) -> bool {
// We arbitrarily use EPSILON^2 as the accuracy margin for approximate equality.
self.abs() < <$ty>::EPSILON * <$ty>::EPSILON
}
}
)*
};
}
impl_num_traits_extended_for_floats!(f32, f64);
macro_rules! impl_num_traits_extended_for_non_floats {
($($ty:ty),*) => {
$(
impl NumTraitsExtended for $ty {
fn is_finite_value(&self) -> bool {
true
}
fn is_at_shrink_target(&self) -> bool {
*self == <$ty>::default()
}
}
)*
};
}
impl_num_traits_extended_for_non_floats!(
i8,
i16,
i32,
i64,
i128,
isize,
u8,
u16,
u32,
u64,
u128,
usize,
bool,
char,
()
);
fn test_arbitrary_mutations_change_value<T>()
where
for<'user> Arbitrary<T>: Domain<UserValue<'user> = T>,
CorpusValueForArbitrary<T>:
std::fmt::Debug + Default + Clone + Copy + PartialOrd + PartialEq + 'static,
{
let domain = Arbitrary::<T>::default();
let mut rng = get_rng();
let mut value = domain.init(&mut rng).unwrap();
// Mutation is guaranteed to change the value.
for _ in 0..100 {
let original_value = value;
domain.mutate(&mut value, &mut rng, false).unwrap();
assert_ne!(
value,
original_value,
"Mutation did not change the value for type {}",
std::any::type_name::<T>()
);
}
}
fn test_arbitrary_mutations_generates_diverse_values<T>()
where
for<'user> Arbitrary<T>: Domain<UserValue<'user> = T>,
CorpusValueForArbitrary<T>: std::fmt::Debug
+ Default
+ Clone
+ Copy
+ PartialOrd
+ PartialEq
+ Eq
+ std::hash::Hash
+ 'static,
{
let domain = Arbitrary::<T>::default();
let mut rng = get_rng();
for _ in 0..100 {
let mut value = domain.init(&mut rng).unwrap();
let mut values = HashSet::new();
values.insert(value);
for _ in 0..100 {
domain.mutate(&mut value, &mut rng, false).unwrap();
values.insert(value);
}
assert!(
values.len() >= 50,
"Mutation did not generate diverse values for type {}: found {} distinct values, expected >= 50",
std::any::type_name::<T>(),
values.len()
);
}
}
fn test_arbitrary_shrinking_reduces_and_terminates<T>()
where
for<'user> Arbitrary<T>: Domain<UserValue<'user> = T>,
CorpusValueForArbitrary<T>: std::fmt::Debug
+ Default
+ Clone
+ Copy
+ PartialOrd
+ PartialEq
+ NumTraitsExtended
+ 'static,
{
let domain = Arbitrary::<T>::default();
let mut rng = get_rng();
// Get a value that is not the shrink target
let shrink_target = CorpusValueForArbitrary::<T>::default();
let mut value;
loop {
value = domain.init(&mut rng).unwrap();
if !value.is_at_shrink_target() && value.is_finite_value() {
break;
}
}
for _ in 0..1000 {
let prev_value = value;
domain.mutate(&mut value, &mut rng, true).unwrap();
if value.is_at_shrink_target() {
// Ensure that once the shrink target is reached, further shrinking doesn't change it.
domain.mutate(&mut value, &mut rng, true).unwrap();
assert!(
value.is_at_shrink_target(),
"Shrinking past the target changed the value for type {}",
std::any::type_name::<T>()
);
return;
}
// Check progress
if prev_value > shrink_target {
assert!(
value < prev_value,
"Value should decrease when shrinking towards target from above. Type: {}, Prev: {:?}, Current: {:?}",
std::any::type_name::<T>(),
prev_value,
value
);
assert!(
value >= shrink_target,
"Value should not shrink past the target from above. Type: {}, Prev: {:?}, Current: {:?}, Target: {:?}",
std::any::type_name::<T>(),
prev_value,
value,
shrink_target
);
} else {
// prev_value < shrink_target
assert!(
value > prev_value,
"Value should increase when shrinking towards target from below. Type: {}, Prev: {:?}, Current: {:?}",
std::any::type_name::<T>(),
prev_value,
value
);
assert!(
value <= shrink_target,
"Value should not shrink past the target from below. Type: {}, Prev: {:?}, Current: {:?}, Target: {:?}",
std::any::type_name::<T>(),
prev_value,
value,
shrink_target
);
}
}
panic!(
"Shrinking did not terminate in 1000 iterations for type {}",
std::any::type_name::<T>()
);
}
fn test_arbitrary<T>()
where
for<'user> Arbitrary<T>: Domain<UserValue<'user> = T>,
CorpusValueForArbitrary<T>: std::fmt::Debug
+ Default
+ Clone
+ Copy
+ PartialOrd
+ PartialEq
+ Eq
+ std::hash::Hash
+ NumTraitsExtended
+ 'static,
{
test_arbitrary_mutations_change_value::<T>();
test_arbitrary_mutations_generates_diverse_values::<T>();
test_arbitrary_shrinking_reduces_and_terminates::<T>();
}
#[test]
fn test_i8() {
test_arbitrary::<i8>();
}
#[test]
fn test_u8() {
test_arbitrary::<u8>();
}
#[test]
fn test_i16() {
test_arbitrary::<i16>();
}
#[test]
fn test_u16() {
test_arbitrary::<u16>();
}
#[test]
fn test_i32() {
test_arbitrary::<i32>();
}
#[test]
fn test_u32() {
test_arbitrary::<u32>();
}
#[test]
fn test_i64() {
test_arbitrary::<i64>();
}
#[test]
fn test_u64() {
test_arbitrary::<u64>();
}
#[test]
fn test_i128() {
test_arbitrary::<i128>();
}
#[test]
fn test_u128() {
test_arbitrary::<u128>();
}
#[test]
fn test_isize() {
test_arbitrary::<isize>();
}
#[test]
fn test_usize() {
test_arbitrary::<usize>();
}
#[test]
fn test_char() {
test_arbitrary::<char>();
}
fn test_bool_shrink() {
let domain = Arbitrary::<bool>::default();
let mut rng = get_rng();
let mut value = true;
domain.mutate(&mut value, &mut rng, true).unwrap();
assert_eq!(value, false);
domain.mutate(&mut value, &mut rng, true).unwrap();
assert_eq!(value, false);
}
#[test]
fn test_bool() {
test_arbitrary_shrinking_reduces_and_terminates::<bool>();
test_bool_shrink();
}
#[test]
fn test_unit() {
let mut rng = get_rng();
let domain = Arbitrary::<()>::default();
// init() always returns ()
assert_eq!(domain.init(&mut rng).unwrap(), ());
// mutate (non-shrinking) always returns ()
let mut value = ();
domain.mutate(&mut value, &mut rng, false).unwrap();
assert_eq!(value, ());
// mutate (shrinking) always returns ()
let mut value = ();
domain.mutate(&mut value, &mut rng, true).unwrap();
assert_eq!(value, ());
}
fn test_float_special_value_shrink<T>()
where
for<'user> Arbitrary<T>: Domain<UserValue<'user> = T>,
CorpusValueForArbitrary<T>:
Float + SampleUniform + std::fmt::Display + std::fmt::Debug + SpecialValues + 'static,
StandardUniform: Distribution<T>,
{
let domain = Arbitrary::<T>::default();
let mut rng = get_rng();
// Positive.
let mut v = CorpusValueForArbitrary::<T>::one();
let original_v = v;
domain.mutate(&mut v, &mut rng, true).unwrap();
assert!(
v >= CorpusValueForArbitrary::<T>::zero() && v < original_v,
"Shrinking {} to {}",
original_v,
v
);
// Negative.
let mut v = -CorpusValueForArbitrary::<T>::one();
let original_v = v;
domain.mutate(&mut v, &mut rng, true).unwrap();
assert!(
v > original_v && v <= CorpusValueForArbitrary::<T>::zero(),
"Shrinking {} to {}",
original_v,
v
);
// Zero.
let mut v = CorpusValueForArbitrary::<T>::zero();
domain.mutate(&mut v, &mut rng, true).unwrap();
assert_eq!(v, CorpusValueForArbitrary::<T>::zero());
let mut v = CorpusValueForArbitrary::<T>::neg_zero();
domain.mutate(&mut v, &mut rng, true).unwrap();
assert_eq!(v, CorpusValueForArbitrary::<T>::neg_zero());
// NaN / Inf are not changed by this shrink implementation.
let mut v = CorpusValueForArbitrary::<T>::nan();
domain.mutate(&mut v, &mut rng, true).unwrap();
assert!(v.is_nan(), "NaN should remain NaN");
let mut v = CorpusValueForArbitrary::<T>::infinity();
domain.mutate(&mut v, &mut rng, true).unwrap();
assert!(v.is_infinite() && v.is_sign_positive(), "Infinity should remain Infinity");
let mut v = CorpusValueForArbitrary::<T>::neg_infinity();
domain.mutate(&mut v, &mut rng, true).unwrap();
assert!(v.is_infinite() && v.is_sign_negative(), "Neg Infinity should remain Neg Infinity");
}
#[test]
fn test_f32() {
test_arbitrary_mutations_change_value::<f32>();
test_arbitrary_shrinking_reduces_and_terminates::<f32>();
test_float_special_value_shrink::<f32>();
}
#[test]
fn test_f64() {
test_arbitrary_mutations_change_value::<f64>();
test_arbitrary_shrinking_reduces_and_terminates::<f64>();
test_float_special_value_shrink::<f64>();
}
#[test]
fn test_char_mutate_boundaries() {
let domain = Arbitrary::<char>::default();
let mut rng = get_rng();
let mut val = '\u{0000}';
domain.mutate(&mut val, &mut rng, false).unwrap();
assert!(val >= '\u{0000}');
val = '\u{10FFFF}';
domain.mutate(&mut val, &mut rng, false).unwrap();
assert!(val <= '\u{10FFFF}');
val = '\u{D7FF}'; // Before surrogate
domain.mutate(&mut val, &mut rng, false).unwrap();
val = '\u{E000}'; // After surrogate
domain.mutate(&mut val, &mut rng, false).unwrap();
}
#[test]
fn test_char_mapping() {
// Test boundary conditions and values around the surrogate range
assert_eq!(map_char_to_int('\u{0000}'), 0);
assert_eq!(map_int_to_char(0), '\u{0000}');
let before_surrogate = SURROGATE_START - 1;
assert_eq!(
map_char_to_int(std::char::from_u32(before_surrogate).unwrap()),
before_surrogate
);
assert_eq!(
map_int_to_char(before_surrogate),
std::char::from_u32(before_surrogate).unwrap()
);
let after_surrogate = SURROGATE_END + 1;
let mapped_after_surrogate = map_char_to_int(std::char::from_u32(after_surrogate).unwrap());
assert_eq!(mapped_after_surrogate, SURROGATE_START);
assert_eq!(map_int_to_char(SURROGATE_START), std::char::from_u32(after_surrogate).unwrap());
assert_eq!(map_char_to_int('\u{10FFFF}'), NUM_VALID_CODEPOINTS - 1);
assert_eq!(map_int_to_char(NUM_VALID_CODEPOINTS - 1), '\u{10FFFF}');
}
#[test]
fn test_char_shrink_to_null() {
for _ in 0..10 {
let domain = Arbitrary::<char>::default();
let mut rng = get_rng();
let mut value = domain.init(&mut rng).unwrap();
let mut iterations = 0;
const MAX_ITERATIONS: u32 = 100_000;
while value != '\0' && iterations < MAX_ITERATIONS {
domain.mutate(&mut value, &mut rng, true).unwrap();
// Ensure that the value is always a valid char after mutation.
assert!(std::char::from_u32(value as u32).is_some());
iterations += 1;
}
assert_eq!(
value, '\0',
"Char did not shrink to null within {} iterations. Final value: {}",
MAX_ITERATIONS, value
);
}
}
}