From 4639f5adffb20b3f3fa6c1aebefa6a9152f7718d Mon Sep 17 00:00:00 2001 From: Brian Anderson <andersrb@gmail.com> Date: Thu, 26 Oct 2023 15:00:33 -0600 Subject: [PATCH] Generate arbitrary containers with heterogeneous elements (#1123) ### What When fuzzing, sometimes generate vecs and maps with elements of the wrong type. These types are generated 1 in every 1000 iterations. ### Why It's a weird case that is possible for contracts to hit in production, so should be generated during fuzzing. Part of https://github.com/stellar/rs-soroban-sdk/issues/1052 ### Known limitations None. --- soroban-sdk/src/arbitrary.rs | 196 ++++++++++++++++++++++++++++++++--- 1 file changed, 182 insertions(+), 14 deletions(-) diff --git a/soroban-sdk/src/arbitrary.rs b/soroban-sdk/src/arbitrary.rs index 8105141a3..2a0ce36f0 100644 --- a/soroban-sdk/src/arbitrary.rs +++ b/soroban-sdk/src/arbitrary.rs @@ -317,6 +317,8 @@ mod objects { use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; use crate::arbitrary::api::*; + use crate::arbitrary::composite::ArbitraryVal; + use crate::env::FromVal; use crate::ConversionError; use crate::{Env, IntoVal, TryFromVal}; @@ -469,9 +471,27 @@ mod objects { ////////////////////////////////// - #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] - pub struct ArbitraryVec<T> { - vec: RustVec<T>, + #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub enum ArbitraryVec<T> { + Good(RustVec<T>), + // Vec<T> can be constructed with non-T values. + Wrong(RustVec<ArbitraryVal>), + } + + impl<'a, T> Arbitrary<'a> for ArbitraryVec<T> + where + T: Arbitrary<'a>, + { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<ArbitraryVec<T>> { + // How frequently we provide ArbitraryVec::Wrong + const WRONG_TYPE_RATIO: (u16, u16) = (1, 1000); + + if u.ratio(WRONG_TYPE_RATIO.0, WRONG_TYPE_RATIO.1)? { + Ok(ArbitraryVec::Wrong(Arbitrary::arbitrary(u)?)) + } else { + Ok(ArbitraryVec::Good(Arbitrary::arbitrary(u)?)) + } + } } impl<T> SorobanArbitrary for Vec<T> @@ -487,19 +507,54 @@ mod objects { { type Error = ConversionError; fn try_from_val(env: &Env, v: &ArbitraryVec<T::Prototype>) -> Result<Self, Self::Error> { - let mut buf: Vec<T> = Vec::new(env); - for item in v.vec.iter() { - buf.push_back(item.into_val(env)); + match v { + ArbitraryVec::Good(vec) => { + let mut buf: Vec<T> = Vec::new(env); + for item in vec.iter() { + buf.push_back(item.into_val(env)); + } + Ok(buf) + } + ArbitraryVec::Wrong(vec) => { + let mut buf: Vec<Val> = Vec::new(env); + for item in vec.iter() { + buf.push_back(item.into_val(env)); + } + Ok(Vec::<T>::from_val(env, &buf.to_val())) + } } - Ok(buf) } } ////////////////////////////////// - #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] - pub struct ArbitraryMap<K, V> { - map: RustVec<(K, V)>, + #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub enum ArbitraryMap<K, V> { + Good(RustVec<(K, V)>), + // Maps can be constructed with non-T K/Vs + WrongKey(RustVec<(ArbitraryVal, V)>), + WrongValue(RustVec<(K, ArbitraryVal)>), + } + + impl<'a, K, V> Arbitrary<'a> for ArbitraryMap<K, V> + where + K: Arbitrary<'a>, + V: Arbitrary<'a>, + { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<ArbitraryMap<K, V>> { + // How frequently we provide ArbitraryMap::Wrong* + const WRONG_TYPE_RATIO: (u16, u16) = (1, 1000); + + if u.ratio(WRONG_TYPE_RATIO.0, WRONG_TYPE_RATIO.1)? { + if u.arbitrary::<bool>()? { + Ok(ArbitraryMap::WrongKey(Arbitrary::arbitrary(u)?)) + } else { + Ok(ArbitraryMap::WrongValue(Arbitrary::arbitrary(u)?)) + } + } else { + Ok(ArbitraryMap::Good(Arbitrary::arbitrary(u)?)) + } + } } impl<K, V> SorobanArbitrary for Map<K, V> @@ -520,11 +575,29 @@ mod objects { env: &Env, v: &ArbitraryMap<K::Prototype, V::Prototype>, ) -> Result<Self, Self::Error> { - let mut map: Map<K, V> = Map::new(env); - for (k, v) in v.map.iter() { - map.set(k.into_val(env), v.into_val(env)); + match v { + ArbitraryMap::Good(vec) => { + let mut map: Map<K, V> = Map::new(env); + for (k, v) in vec.iter() { + map.set(k.into_val(env), v.into_val(env)); + } + Ok(map) + } + ArbitraryMap::WrongKey(vec) => { + let mut map: Map<Val, V> = Map::new(env); + for (k, v) in vec.iter() { + map.set(k.into_val(env), v.into_val(env)); + } + Ok(Map::<K, V>::from_val(env, &map.to_val())) + } + ArbitraryMap::WrongValue(vec) => { + let mut map: Map<K, Val> = Map::new(env); + for (k, v) in vec.iter() { + map.set(k.into_val(env), v.into_val(env)); + } + Ok(Map::<K, V>::from_val(env, &map.to_val())) + } } - Ok(map) } } @@ -1300,6 +1373,101 @@ mod tests { run_test::<Duration>() } + // Test that sometimes generated vecs have the wrong element types. + #[test] + fn test_vec_wrong_types() { + // These number are tuned for StdRng. + // If StdRng ever changes the test could break. + let iterations = 1000; + let seed = 3; + let acceptable_ratio = 900; + + let (mut seen_good, mut seen_bad, mut seen_empty) = (0, 0, 0); + + let env = Env::default(); + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + let mut rng_data = [0u8; 64]; + + for _ in 0..iterations { + rng.fill_bytes(&mut rng_data); + let mut unstructured = Unstructured::new(&rng_data); + let input = <Vec<u32> as SorobanArbitrary>::Prototype::arbitrary(&mut unstructured) + .expect("SorobanArbitrary"); + let vec: Vec<u32> = input.into_val(&env); + + let has_good_elts = (0..vec.len()).all(|i| vec.try_get(i).is_ok()) && !vec.is_empty(); + // Look for elements that cause an error. + let has_bad_elt = (0..vec.len()).any(|i| vec.try_get(i).is_err()); + + if has_bad_elt { + seen_bad += 1; + } else if has_good_elts { + seen_good += 1; + } else { + seen_empty += 1; + } + } + + assert!(seen_good > 0); + assert!(seen_bad > 0); + + // sanity check the ratio of good to bad + assert!(seen_good * seen_empty > seen_bad * acceptable_ratio); + } + + // Test that sometimes generated maps have the wrong element types. + #[test] + fn test_map_wrong_types() { + // These number are tuned for StdRng. + // If StdRng ever changes the test could break. + let iterations = 4000; + let seed = 13; + let acceptable_ratio = 900; + + let (mut seen_good, mut seen_bad_key, mut seen_bad_value, mut seen_empty) = (0, 0, 0, 0); + + let env = Env::default(); + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + let mut rng_data = [0u8; 128]; + + for _ in 0..iterations { + rng.fill_bytes(&mut rng_data); + let mut unstructured = Unstructured::new(&rng_data); + let input = + <Map<u32, u32> as SorobanArbitrary>::Prototype::arbitrary(&mut unstructured) + .expect("SorobanArbitrary"); + let map: Map<u32, u32> = input.into_val(&env); + + // Look for elements that cause an error. + let keys = map.keys(); + let values = map.values(); + + let has_good_keys = + (0..keys.len()).all(|i| keys.try_get(i).is_ok()) && !keys.is_empty(); + let has_good_values = + (0..values.len()).all(|i| values.try_get(i).is_ok()) && !keys.is_empty(); + let has_bad_key = (0..keys.len()).any(|i| keys.try_get(i).is_err()); + let has_bad_value = (0..values.len()).any(|i| values.try_get(i).is_err()); + + if has_bad_key { + seen_bad_key += 1; + } else if has_bad_value { + seen_bad_value += 1; + } else if has_good_keys && has_good_values { + seen_good += 1; + } else { + seen_empty += 1; + } + } + + assert!(seen_good > 0); + assert!(seen_bad_key > 0); + assert!(seen_bad_value > 0); + + // sanity check the ratio of good to bad + assert!(seen_good * seen_empty > (seen_bad_key + seen_bad_value) * acceptable_ratio); + } + mod user_defined_types { use crate as soroban_sdk; use crate::arbitrary::tests::run_test;