Path: blob/main/crates/wiggle/test-helpers/src/lib.rs
1692 views
use proptest::prelude::*;1use std::marker;2use wiggle::GuestMemory;34#[derive(Debug, Clone)]5pub struct MemAreas(Vec<MemArea>);6impl MemAreas {7pub fn new() -> Self {8MemAreas(Vec::new())9}10pub fn insert(&mut self, a: MemArea) {11// Find if `a` is already in the vector12match self.0.binary_search(&a) {13// It is present - insert it next to existing one14Ok(loc) => self.0.insert(loc, a),15// It is not present - heres where to insert it16Err(loc) => self.0.insert(loc, a),17}18}19pub fn iter(&self) -> impl Iterator<Item = &MemArea> {20self.0.iter()21}22}2324impl<R> From<R> for MemAreas25where26R: AsRef<[MemArea]>,27{28fn from(ms: R) -> MemAreas {29let mut out = MemAreas::new();30for m in ms.as_ref().into_iter() {31out.insert(*m);32}33out34}35}3637impl From<MemAreas> for Vec<MemArea> {38fn from(areas: MemAreas) -> Vec<MemArea> {39areas.0.clone()40}41}4243#[repr(align(4096))]44struct HostBuffer {45cell: [u8; 4096],46}4748unsafe impl Send for HostBuffer {}49unsafe impl Sync for HostBuffer {}5051pub struct HostMemory {52buffer: HostBuffer,53}54impl HostMemory {55pub fn new() -> Self {56HostMemory {57buffer: HostBuffer { cell: [0; 4096] },58}59}6061pub fn guest_memory(&mut self) -> GuestMemory<'_> {62GuestMemory::Unshared(&mut self.buffer.cell)63}6465pub fn base(&self) -> *const u8 {66self.buffer.cell.as_ptr()67}6869pub fn mem_area_strat(align: u32) -> BoxedStrategy<MemArea> {70prop::num::u32::ANY71.prop_filter_map("needs to fit in memory", move |p| {72let p_aligned = p - (p % align); // Align according to argument73let ptr = p_aligned % 4096; // Put inside memory74if ptr + align < 4096 {75Some(MemArea { ptr, len: align })76} else {77None78}79})80.boxed()81}8283/// Takes a sorted list or memareas, and gives a sorted list of memareas covering84/// the parts of memory not covered by the previous85pub fn invert(regions: &MemAreas) -> MemAreas {86let mut out = MemAreas::new();87let mut start = 0;88for r in regions.iter() {89let len = r.ptr - start;90if len > 0 {91out.insert(MemArea {92ptr: start,93len: r.ptr - start,94});95}96start = r.ptr + r.len;97}98if start < 4096 {99out.insert(MemArea {100ptr: start,101len: 4096 - start,102});103}104out105}106107pub fn byte_slice_strat(size: u32, align: u32, exclude: &MemAreas) -> BoxedStrategy<MemArea> {108let available: Vec<MemArea> = Self::invert(exclude)109.iter()110.flat_map(|a| a.inside(size))111.filter(|a| a.ptr % align == 0)112.collect();113114Just(available)115.prop_filter("available memory for allocation", |a| !a.is_empty())116.prop_flat_map(|a| prop::sample::select(a))117.boxed()118}119}120121#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]122pub struct MemArea {123pub ptr: u32,124pub len: u32,125}126127impl MemArea {128// This code is a whole lot like the Region::overlaps func that's at the core of the code under129// test.130// So, I implemented this one with std::ops::Range so it is less likely I wrote the same bug in two131// places.132pub fn overlapping(&self, b: Self) -> bool {133// a_range is all elems in A134let a_range = std::ops::Range {135start: self.ptr,136end: self.ptr + self.len, // std::ops::Range is open from the right137};138// b_range is all elems in B139let b_range = std::ops::Range {140start: b.ptr,141end: b.ptr + b.len,142};143// No element in B is contained in A:144for b_elem in b_range.clone() {145if a_range.contains(&b_elem) {146return true;147}148}149// No element in A is contained in B:150for a_elem in a_range {151if b_range.contains(&a_elem) {152return true;153}154}155return false;156}157pub fn non_overlapping_set<M>(areas: M) -> bool158where159M: Into<MemAreas>,160{161let areas = areas.into();162for (aix, a) in areas.iter().enumerate() {163for (bix, b) in areas.iter().enumerate() {164if aix != bix {165// (A, B) is every pairing of areas166if a.overlapping(*b) {167return false;168}169}170}171}172return true;173}174175/// Enumerate all memareas of size `len` inside a given area176fn inside(&self, len: u32) -> impl Iterator<Item = MemArea> + use<> {177let end: i64 = self.len as i64 - len as i64;178let start = self.ptr;179(0..end).map(move |v| MemArea {180ptr: start + v as u32,181len,182})183}184}185186#[cfg(test)]187mod test {188use super::*;189190#[test]191fn hostmemory_is_aligned() {192let h = HostMemory::new();193assert_eq!(h.base() as usize % 4096, 0);194let h = Box::new(h);195assert_eq!(h.base() as usize % 4096, 0);196}197198#[test]199fn invert() {200fn invert_equality(input: &[MemArea], expected: &[MemArea]) {201let input: MemAreas = input.into();202let inverted: Vec<MemArea> = HostMemory::invert(&input).into();203assert_eq!(expected, inverted.as_slice());204}205206invert_equality(&[], &[MemArea { ptr: 0, len: 4096 }]);207invert_equality(208&[MemArea { ptr: 0, len: 1 }],209&[MemArea { ptr: 1, len: 4095 }],210);211212invert_equality(213&[MemArea { ptr: 1, len: 1 }],214&[MemArea { ptr: 0, len: 1 }, MemArea { ptr: 2, len: 4094 }],215);216217invert_equality(218&[MemArea { ptr: 1, len: 4095 }],219&[MemArea { ptr: 0, len: 1 }],220);221222invert_equality(223&[MemArea { ptr: 0, len: 1 }, MemArea { ptr: 1, len: 4095 }],224&[],225);226227invert_equality(228&[MemArea { ptr: 1, len: 2 }, MemArea { ptr: 4, len: 1 }],229&[230MemArea { ptr: 0, len: 1 },231MemArea { ptr: 3, len: 1 },232MemArea { ptr: 5, len: 4091 },233],234);235}236237fn set_of_slices_strat(238s1: u32,239s2: u32,240s3: u32,241) -> BoxedStrategy<(MemArea, MemArea, MemArea)> {242HostMemory::byte_slice_strat(s1, 1, &MemAreas::new())243.prop_flat_map(move |a1| {244(245Just(a1),246HostMemory::byte_slice_strat(s2, 1, &MemAreas::from(&[a1])),247)248})249.prop_flat_map(move |(a1, a2)| {250(251Just(a1),252Just(a2),253HostMemory::byte_slice_strat(s3, 1, &MemAreas::from(&[a1, a2])),254)255})256.boxed()257}258259#[test]260fn trivial_inside() {261let a = MemArea { ptr: 24, len: 4072 };262let interior = a.inside(24).collect::<Vec<_>>();263264assert!(interior.len() > 0);265}266267proptest! {268#[test]269// For some random region of decent size270fn inside(r in HostMemory::mem_area_strat(123)) {271let set_of_r = MemAreas::from(&[r]);272// All regions outside of r:273let exterior = HostMemory::invert(&set_of_r);274// All regions inside of r:275let interior = r.inside(22);276for i in interior {277// i overlaps with r:278assert!(r.overlapping(i));279// i is inside r:280assert!(i.ptr >= r.ptr);281assert!(r.ptr + r.len >= i.ptr + i.len);282// the set of exterior and i is non-overlapping283let mut all = exterior.clone();284all.insert(i);285assert!(MemArea::non_overlapping_set(all));286}287}288289#[test]290fn byte_slices((s1, s2, s3) in set_of_slices_strat(12, 34, 56)) {291let all = MemAreas::from(&[s1, s2, s3]);292assert!(MemArea::non_overlapping_set(all));293}294}295}296297use std::cell::RefCell;298use wiggle::GuestError;299300// In lucet, our Ctx struct needs a lifetime, so we're using one301// on the test as well.302pub struct WasiCtx<'a> {303pub guest_errors: RefCell<Vec<GuestError>>,304pub log: RefCell<Vec<String>>,305lifetime: marker::PhantomData<&'a ()>,306}307308impl<'a> WasiCtx<'a> {309pub fn new() -> Self {310Self {311guest_errors: RefCell::new(vec![]),312log: RefCell::new(vec![]),313lifetime: marker::PhantomData,314}315}316}317318// Errno is used as a first return value in the functions above, therefore319// it must implement GuestErrorType with type Context = WasiCtx.320// The context type should let you do logging or debugging or whatever you need321// with these errors. We just push them to vecs.322#[macro_export]323macro_rules! impl_errno {324( $errno:ty ) => {325impl wiggle::GuestErrorType for $errno {326fn success() -> $errno {327<$errno>::Ok328}329}330};331}332333334