wiggle_test/
lib.rs

1use proptest::prelude::*;
2use std::marker;
3use wiggle::GuestMemory;
4
5#[derive(Debug, Clone)]
6pub struct MemAreas(Vec<MemArea>);
7impl MemAreas {
8    pub fn new() -> Self {
9        MemAreas(Vec::new())
10    }
11    pub fn insert(&mut self, a: MemArea) {
12        // Find if `a` is already in the vector
13        match self.0.binary_search(&a) {
14            // It is present - insert it next to existing one
15            Ok(loc) => self.0.insert(loc, a),
16            // It is not present - heres where to insert it
17            Err(loc) => self.0.insert(loc, a),
18        }
19    }
20    pub fn iter(&self) -> impl Iterator<Item = &MemArea> {
21        self.0.iter()
22    }
23}
24
25impl<R> From<R> for MemAreas
26where
27    R: AsRef<[MemArea]>,
28{
29    fn from(ms: R) -> MemAreas {
30        let mut out = MemAreas::new();
31        for m in ms.as_ref().into_iter() {
32            out.insert(*m);
33        }
34        out
35    }
36}
37
38impl Into<Vec<MemArea>> for MemAreas {
39    fn into(self) -> Vec<MemArea> {
40        self.0.clone()
41    }
42}
43
44#[repr(align(4096))]
45struct HostBuffer {
46    cell: [u8; 4096],
47}
48
49unsafe impl Send for HostBuffer {}
50unsafe impl Sync for HostBuffer {}
51
52pub struct HostMemory {
53    buffer: HostBuffer,
54}
55impl HostMemory {
56    pub fn new() -> Self {
57        HostMemory {
58            buffer: HostBuffer { cell: [0; 4096] },
59        }
60    }
61
62    pub fn guest_memory(&mut self) -> GuestMemory<'_> {
63        GuestMemory::Unshared(&mut self.buffer.cell)
64    }
65
66    pub fn base(&self) -> *const u8 {
67        self.buffer.cell.as_ptr()
68    }
69
70    pub fn mem_area_strat(align: u32) -> BoxedStrategy<MemArea> {
71        prop::num::u32::ANY
72            .prop_filter_map("needs to fit in memory", move |p| {
73                let p_aligned = p - (p % align); // Align according to argument
74                let ptr = p_aligned % 4096; // Put inside memory
75                if ptr + align < 4096 {
76                    Some(MemArea { ptr, len: align })
77                } else {
78                    None
79                }
80            })
81            .boxed()
82    }
83
84    /// Takes a sorted list or memareas, and gives a sorted list of memareas covering
85    /// the parts of memory not covered by the previous
86    pub fn invert(regions: &MemAreas) -> MemAreas {
87        let mut out = MemAreas::new();
88        let mut start = 0;
89        for r in regions.iter() {
90            let len = r.ptr - start;
91            if len > 0 {
92                out.insert(MemArea {
93                    ptr: start,
94                    len: r.ptr - start,
95                });
96            }
97            start = r.ptr + r.len;
98        }
99        if start < 4096 {
100            out.insert(MemArea {
101                ptr: start,
102                len: 4096 - start,
103            });
104        }
105        out
106    }
107
108    pub fn byte_slice_strat(size: u32, align: u32, exclude: &MemAreas) -> BoxedStrategy<MemArea> {
109        let available: Vec<MemArea> = Self::invert(exclude)
110            .iter()
111            .flat_map(|a| a.inside(size))
112            .filter(|a| a.ptr % align == 0)
113            .collect();
114
115        Just(available)
116            .prop_filter("available memory for allocation", |a| !a.is_empty())
117            .prop_flat_map(|a| prop::sample::select(a))
118            .boxed()
119    }
120}
121
122#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
123pub struct MemArea {
124    pub ptr: u32,
125    pub len: u32,
126}
127
128impl MemArea {
129    // This code is a whole lot like the Region::overlaps func that's at the core of the code under
130    // test.
131    // So, I implemented this one with std::ops::Range so it is less likely I wrote the same bug in two
132    // places.
133    pub fn overlapping(&self, b: Self) -> bool {
134        // a_range is all elems in A
135        let a_range = std::ops::Range {
136            start: self.ptr,
137            end: self.ptr + self.len, // std::ops::Range is open from the right
138        };
139        // b_range is all elems in B
140        let b_range = std::ops::Range {
141            start: b.ptr,
142            end: b.ptr + b.len,
143        };
144        // No element in B is contained in A:
145        for b_elem in b_range.clone() {
146            if a_range.contains(&b_elem) {
147                return true;
148            }
149        }
150        // No element in A is contained in B:
151        for a_elem in a_range {
152            if b_range.contains(&a_elem) {
153                return true;
154            }
155        }
156        return false;
157    }
158    pub fn non_overlapping_set<M>(areas: M) -> bool
159    where
160        M: Into<MemAreas>,
161    {
162        let areas = areas.into();
163        for (aix, a) in areas.iter().enumerate() {
164            for (bix, b) in areas.iter().enumerate() {
165                if aix != bix {
166                    // (A, B) is every pairing of areas
167                    if a.overlapping(*b) {
168                        return false;
169                    }
170                }
171            }
172        }
173        return true;
174    }
175
176    /// Enumerate all memareas of size `len` inside a given area
177    fn inside(&self, len: u32) -> impl Iterator<Item = MemArea> + use<> {
178        let end: i64 = self.len as i64 - len as i64;
179        let start = self.ptr;
180        (0..end).into_iter().map(move |v| MemArea {
181            ptr: start + v as u32,
182            len,
183        })
184    }
185}
186
187#[cfg(test)]
188mod test {
189    use super::*;
190
191    #[test]
192    fn hostmemory_is_aligned() {
193        let h = HostMemory::new();
194        assert_eq!(h.base() as usize % 4096, 0);
195        let h = Box::new(h);
196        assert_eq!(h.base() as usize % 4096, 0);
197    }
198
199    #[test]
200    fn invert() {
201        fn invert_equality(input: &[MemArea], expected: &[MemArea]) {
202            let input: MemAreas = input.into();
203            let inverted: Vec<MemArea> = HostMemory::invert(&input).into();
204            assert_eq!(expected, inverted.as_slice());
205        }
206
207        invert_equality(&[], &[MemArea { ptr: 0, len: 4096 }]);
208        invert_equality(
209            &[MemArea { ptr: 0, len: 1 }],
210            &[MemArea { ptr: 1, len: 4095 }],
211        );
212
213        invert_equality(
214            &[MemArea { ptr: 1, len: 1 }],
215            &[MemArea { ptr: 0, len: 1 }, MemArea { ptr: 2, len: 4094 }],
216        );
217
218        invert_equality(
219            &[MemArea { ptr: 1, len: 4095 }],
220            &[MemArea { ptr: 0, len: 1 }],
221        );
222
223        invert_equality(
224            &[MemArea { ptr: 0, len: 1 }, MemArea { ptr: 1, len: 4095 }],
225            &[],
226        );
227
228        invert_equality(
229            &[MemArea { ptr: 1, len: 2 }, MemArea { ptr: 4, len: 1 }],
230            &[
231                MemArea { ptr: 0, len: 1 },
232                MemArea { ptr: 3, len: 1 },
233                MemArea { ptr: 5, len: 4091 },
234            ],
235        );
236    }
237
238    fn set_of_slices_strat(
239        s1: u32,
240        s2: u32,
241        s3: u32,
242    ) -> BoxedStrategy<(MemArea, MemArea, MemArea)> {
243        HostMemory::byte_slice_strat(s1, 1, &MemAreas::new())
244            .prop_flat_map(move |a1| {
245                (
246                    Just(a1),
247                    HostMemory::byte_slice_strat(s2, 1, &MemAreas::from(&[a1])),
248                )
249            })
250            .prop_flat_map(move |(a1, a2)| {
251                (
252                    Just(a1),
253                    Just(a2),
254                    HostMemory::byte_slice_strat(s3, 1, &MemAreas::from(&[a1, a2])),
255                )
256            })
257            .boxed()
258    }
259
260    #[test]
261    fn trivial_inside() {
262        let a = MemArea { ptr: 24, len: 4072 };
263        let interior = a.inside(24).collect::<Vec<_>>();
264
265        assert!(interior.len() > 0);
266    }
267
268    proptest! {
269        #[test]
270        // For some random region of decent size
271        fn inside(r in HostMemory::mem_area_strat(123)) {
272            let set_of_r = MemAreas::from(&[r]);
273            // All regions outside of r:
274            let exterior = HostMemory::invert(&set_of_r);
275            // All regions inside of r:
276            let interior = r.inside(22);
277            for i in interior {
278                // i overlaps with r:
279                assert!(r.overlapping(i));
280                // i is inside r:
281                assert!(i.ptr >= r.ptr);
282                assert!(r.ptr + r.len >= i.ptr + i.len);
283                // the set of exterior and i is non-overlapping
284                let mut all = exterior.clone();
285                all.insert(i);
286                assert!(MemArea::non_overlapping_set(all));
287            }
288        }
289
290        #[test]
291        fn byte_slices((s1, s2, s3) in set_of_slices_strat(12, 34, 56)) {
292            let all = MemAreas::from(&[s1, s2, s3]);
293            assert!(MemArea::non_overlapping_set(all));
294        }
295    }
296}
297
298use std::cell::RefCell;
299use wiggle::GuestError;
300
301// In lucet, our Ctx struct needs a lifetime, so we're using one
302// on the test as well.
303pub struct WasiCtx<'a> {
304    pub guest_errors: RefCell<Vec<GuestError>>,
305    pub log: RefCell<Vec<String>>,
306    lifetime: marker::PhantomData<&'a ()>,
307}
308
309impl<'a> WasiCtx<'a> {
310    pub fn new() -> Self {
311        Self {
312            guest_errors: RefCell::new(vec![]),
313            log: RefCell::new(vec![]),
314            lifetime: marker::PhantomData,
315        }
316    }
317}
318
319// Errno is used as a first return value in the functions above, therefore
320// it must implement GuestErrorType with type Context = WasiCtx.
321// The context type should let you do logging or debugging or whatever you need
322// with these errors. We just push them to vecs.
323#[macro_export]
324macro_rules! impl_errno {
325    ( $errno:ty ) => {
326        impl wiggle::GuestErrorType for $errno {
327            fn success() -> $errno {
328                <$errno>::Ok
329            }
330        }
331    };
332}