1 use proptest::prelude::*;
2 use wiggle::{GuestMemory, GuestPtr, GuestType};
3 use wiggle_test::{HostMemory, MemArea, MemAreas, WasiCtx, impl_errno};
4 
5 wiggle::from_witx!({
6     witx: ["tests/lists.witx"],
7 });
8 
9 impl_errno!(types::Errno);
10 
11 impl<'a> lists::Lists for WasiCtx<'a> {
reduce_excuses( &mut self, memory: &mut GuestMemory<'_>, excuses: types::ConstExcuseArray, ) -> Result<types::Excuse, types::Errno>12     fn reduce_excuses(
13         &mut self,
14         memory: &mut GuestMemory<'_>,
15         excuses: types::ConstExcuseArray,
16     ) -> Result<types::Excuse, types::Errno> {
17         let last = memory
18             .read(
19                 excuses
20                     .iter()
21                     .last()
22                     .expect("input array is non-empty")
23                     .expect("valid ptr to ptr"),
24             )
25             .expect("valid ptr to some Excuse value");
26         Ok(memory.read(last).expect("dereferencing ptr should succeed"))
27     }
28 
populate_excuses( &mut self, memory: &mut GuestMemory<'_>, excuses: types::ExcuseArray, ) -> Result<(), types::Errno>29     fn populate_excuses(
30         &mut self,
31         memory: &mut GuestMemory<'_>,
32         excuses: types::ExcuseArray,
33     ) -> Result<(), types::Errno> {
34         for excuse in excuses.iter() {
35             let ptr_to_excuse = memory
36                 .read(excuse.expect("valid ptr to ptr"))
37                 .expect("valid ptr to some Excuse value");
38             memory
39                 .write(ptr_to_excuse, types::Excuse::Sleeping)
40                 .expect("dereferencing mut ptr should succeed");
41         }
42         Ok(())
43     }
44 }
45 
46 #[derive(Debug)]
47 struct ReduceExcusesExercise {
48     excuse_values: Vec<types::Excuse>,
49     excuse_ptr_locs: Vec<MemArea>,
50     array_ptr_loc: MemArea,
51     return_ptr_loc: MemArea,
52 }
53 
54 impl ReduceExcusesExercise {
strat() -> BoxedStrategy<Self>55     pub fn strat() -> BoxedStrategy<Self> {
56         (1..256u32)
57             .prop_flat_map(|len| {
58                 let len_usize = len as usize;
59                 (
60                     proptest::collection::vec(excuse_strat(), len_usize..=len_usize),
61                     proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize),
62                     HostMemory::mem_area_strat(4 * len),
63                     HostMemory::mem_area_strat(4),
64                 )
65             })
66             .prop_map(
67                 |(excuse_values, excuse_ptr_locs, array_ptr_loc, return_ptr_loc)| Self {
68                     excuse_values,
69                     excuse_ptr_locs,
70                     array_ptr_loc,
71                     return_ptr_loc,
72                 },
73             )
74             .prop_filter("non-overlapping pointers", |e| {
75                 let mut all = vec![e.array_ptr_loc, e.return_ptr_loc];
76                 all.extend(e.excuse_ptr_locs.iter());
77                 MemArea::non_overlapping_set(all)
78             })
79             .boxed()
80     }
81 
test(&self)82     pub fn test(&self) {
83         let mut ctx = WasiCtx::new();
84         let mut host_memory = HostMemory::new();
85         let mut memory = host_memory.guest_memory();
86 
87         // Populate memory with pointers to generated Excuse values
88         for (&excuse, ptr) in self.excuse_values.iter().zip(self.excuse_ptr_locs.iter()) {
89             memory
90                 .write(GuestPtr::new(ptr.ptr), excuse)
91                 .expect("deref ptr mut to Excuse value");
92         }
93 
94         // Populate the array with pointers to generated Excuse values
95         {
96             let array: GuestPtr<[GuestPtr<types::Excuse>]> =
97                 GuestPtr::new((self.array_ptr_loc.ptr, self.excuse_ptr_locs.len() as u32));
98             for (slot, ptr) in array.iter().zip(&self.excuse_ptr_locs) {
99                 let slot = slot.expect("array should be in bounds");
100                 memory
101                     .write(slot, GuestPtr::new(ptr.ptr))
102                     .expect("should succeed in writing array");
103             }
104         }
105 
106         let res = lists::reduce_excuses(
107             &mut ctx,
108             &mut memory,
109             self.array_ptr_loc.ptr as i32,
110             self.excuse_ptr_locs.len() as i32,
111             self.return_ptr_loc.ptr as i32,
112         )
113         .unwrap();
114 
115         assert_eq!(res, types::Errno::Ok as i32, "reduce excuses errno");
116 
117         let expected = *self
118             .excuse_values
119             .last()
120             .expect("generated vec of excuses should be non-empty");
121         let given: types::Excuse = memory
122             .read(GuestPtr::new(self.return_ptr_loc.ptr))
123             .expect("deref ptr to returned value");
124         assert_eq!(expected, given, "reduce excuses return val");
125     }
126 }
127 proptest! {
128     #[test]
129     fn reduce_excuses(e in ReduceExcusesExercise::strat()) {
130         e.test()
131     }
132 }
133 
excuse_strat() -> impl Strategy<Value = types::Excuse>134 fn excuse_strat() -> impl Strategy<Value = types::Excuse> {
135     prop_oneof![
136         Just(types::Excuse::DogAte),
137         Just(types::Excuse::Traffic),
138         Just(types::Excuse::Sleeping),
139     ]
140     .boxed()
141 }
142 
143 #[derive(Debug)]
144 struct PopulateExcusesExercise {
145     array_ptr_loc: MemArea,
146     elements: Vec<MemArea>,
147 }
148 
149 impl PopulateExcusesExercise {
strat() -> BoxedStrategy<Self>150     pub fn strat() -> BoxedStrategy<Self> {
151         (1..256u32)
152             .prop_flat_map(|len| {
153                 let len_usize = len as usize;
154                 (
155                     HostMemory::mem_area_strat(4 * len),
156                     proptest::collection::vec(HostMemory::mem_area_strat(4), len_usize..=len_usize),
157                 )
158             })
159             .prop_map(|(array_ptr_loc, elements)| Self {
160                 array_ptr_loc,
161                 elements,
162             })
163             .prop_filter("non-overlapping pointers", |e| {
164                 let mut all = vec![e.array_ptr_loc];
165                 all.extend(e.elements.iter());
166                 MemArea::non_overlapping_set(all)
167             })
168             .boxed()
169     }
170 
test(&self)171     pub fn test(&self) {
172         let mut ctx = WasiCtx::new();
173         let mut host_memory = HostMemory::new();
174         let mut memory = host_memory.guest_memory();
175 
176         // Populate array with valid pointers to Excuse type in memory
177         let ptr = GuestPtr::<[GuestPtr<types::Excuse>]>::new((
178             self.array_ptr_loc.ptr,
179             self.elements.len() as u32,
180         ));
181         for (ptr, val) in ptr.iter().zip(&self.elements) {
182             memory
183                 .write(
184                     ptr.expect("should be valid pointer"),
185                     GuestPtr::new(val.ptr),
186                 )
187                 .expect("failed to write value");
188         }
189 
190         let res = lists::populate_excuses(
191             &mut ctx,
192             &mut memory,
193             self.array_ptr_loc.ptr as i32,
194             self.elements.len() as i32,
195         )
196         .unwrap();
197         assert_eq!(res, types::Errno::Ok as i32, "populate excuses errno");
198 
199         let arr: GuestPtr<[GuestPtr<types::Excuse>]> =
200             GuestPtr::new((self.array_ptr_loc.ptr, self.elements.len() as u32));
201         for el in arr.iter() {
202             let ptr_to_ptr = memory
203                 .read(el.expect("valid ptr to ptr"))
204                 .expect("valid ptr to some Excuse value");
205             assert_eq!(
206                 memory
207                     .read(ptr_to_ptr)
208                     .expect("dereferencing ptr to some Excuse value"),
209                 types::Excuse::Sleeping,
210                 "element should equal Excuse::Sleeping"
211             );
212         }
213     }
214 }
215 proptest! {
216     #[test]
217     fn populate_excuses(e in PopulateExcusesExercise::strat()) {
218         e.test()
219     }
220 }
221 
222 impl<'a> array_traversal::ArrayTraversal for WasiCtx<'a> {
sum_of_element( &mut self, memory: &mut GuestMemory<'_>, elements: GuestPtr<[types::PairInts]>, index: u32, ) -> Result<i32, types::Errno>223     fn sum_of_element(
224         &mut self,
225         memory: &mut GuestMemory<'_>,
226         elements: GuestPtr<[types::PairInts]>,
227         index: u32,
228     ) -> Result<i32, types::Errno> {
229         let elem_ptr = elements.get(index).ok_or(types::Errno::InvalidArg)?;
230         let pair = memory
231             .read(elem_ptr)
232             .map_err(|_| types::Errno::DontWantTo)?;
233         Ok(pair.first.wrapping_add(pair.second))
234     }
sum_of_elements( &mut self, memory: &mut GuestMemory<'_>, elements: GuestPtr<[types::PairInts]>, start: u32, end: u32, ) -> Result<i32, types::Errno>235     fn sum_of_elements(
236         &mut self,
237         memory: &mut GuestMemory<'_>,
238         elements: GuestPtr<[types::PairInts]>,
239         start: u32,
240         end: u32,
241     ) -> Result<i32, types::Errno> {
242         let elem_range = elements
243             .get_range(start..end)
244             .ok_or(types::Errno::InvalidArg)?;
245         let mut sum: i32 = 0;
246         for e in elem_range.iter() {
247             let pair = memory
248                 .read(e.map_err(|_| types::Errno::DontWantTo)?)
249                 .map_err(|_| types::Errno::PhysicallyUnable)?;
250             sum = sum.wrapping_add(pair.first).wrapping_add(pair.second);
251         }
252         Ok(sum)
253     }
254 }
255 
256 impl types::PairInts {
strat() -> BoxedStrategy<Self>257     pub fn strat() -> BoxedStrategy<Self> {
258         (prop::num::i32::ANY, prop::num::i32::ANY)
259             .prop_map(|(first, second)| types::PairInts { first, second })
260             .boxed()
261     }
262 }
263 
264 #[derive(Debug)]
265 struct SumElementsExercise {
266     elements: Vec<types::PairInts>,
267     element_loc: MemArea,
268     return_loc: MemArea,
269     start_ix: u32,
270     end_ix: u32,
271 }
272 
273 impl SumElementsExercise {
strat() -> BoxedStrategy<Self>274     pub fn strat() -> BoxedStrategy<Self> {
275         (
276             prop::collection::vec(types::PairInts::strat(), 1..256),
277             HostMemory::mem_area_strat(4),
278         )
279             .prop_flat_map(|(elements, return_loc)| {
280                 let len = elements.len() as u32;
281                 (
282                     Just(elements),
283                     HostMemory::byte_slice_strat(
284                         len * types::PairInts::guest_size(),
285                         types::PairInts::guest_size(),
286                         &MemAreas::from([return_loc]),
287                     ),
288                     Just(return_loc),
289                     0..len,
290                     0..len,
291                 )
292             })
293             .prop_map(
294                 |(elements, element_loc, return_loc, start_ix, end_ix)| SumElementsExercise {
295                     elements,
296                     element_loc,
297                     return_loc,
298                     start_ix,
299                     end_ix,
300                 },
301             )
302             .boxed()
303     }
test(&self)304     pub fn test(&self) {
305         let mut ctx = WasiCtx::new();
306         let mut host_memory = HostMemory::new();
307         let mut memory = host_memory.guest_memory();
308 
309         // Populate array
310         let ptr =
311             GuestPtr::<[types::PairInts]>::new((self.element_loc.ptr, self.elements.len() as u32));
312         for (ptr, val) in ptr.iter().zip(&self.elements) {
313             memory
314                 .write(ptr.expect("should be valid pointer"), val.clone())
315                 .expect("failed to write value");
316         }
317 
318         let res = array_traversal::sum_of_element(
319             &mut ctx,
320             &mut memory,
321             self.element_loc.ptr as i32,
322             self.elements.len() as i32,
323             self.start_ix as i32,
324             self.return_loc.ptr as i32,
325         )
326         .unwrap();
327         assert_eq!(res, types::Errno::Ok as i32, "sum_of_element errno");
328         let result_ptr = GuestPtr::<i32>::new(self.return_loc.ptr);
329         let result = memory.read(result_ptr).expect("read result");
330 
331         let e = self
332             .elements
333             .get(self.start_ix as usize)
334             .expect("start_ix must be in bounds");
335         assert_eq!(result, e.first.wrapping_add(e.second), "sum of element");
336 
337         // Off the end of the array:
338         let res = array_traversal::sum_of_element(
339             &mut ctx,
340             &mut memory,
341             self.element_loc.ptr as i32,
342             self.elements.len() as i32,
343             self.elements.len() as i32,
344             self.return_loc.ptr as i32,
345         )
346         .unwrap();
347         assert_eq!(
348             res,
349             types::Errno::InvalidArg as i32,
350             "out of bounds sum_of_element errno"
351         );
352 
353         let res = array_traversal::sum_of_elements(
354             &mut ctx,
355             &mut memory,
356             self.element_loc.ptr as i32,
357             self.elements.len() as i32,
358             self.start_ix as i32,
359             self.end_ix as i32,
360             self.return_loc.ptr as i32,
361         )
362         .unwrap();
363         if self.start_ix <= self.end_ix {
364             assert_eq!(
365                 res,
366                 types::Errno::Ok as i32,
367                 "expected ok sum_of_elements errno"
368             );
369             let result_ptr = GuestPtr::<i32>::new(self.return_loc.ptr);
370             let result = memory.read(result_ptr).expect("read result");
371 
372             let mut expected_sum: i32 = 0;
373             for elem in self
374                 .elements
375                 .get(self.start_ix as usize..self.end_ix as usize)
376                 .unwrap()
377                 .iter()
378             {
379                 expected_sum = expected_sum
380                     .wrapping_add(elem.first)
381                     .wrapping_add(elem.second);
382             }
383             assert_eq!(result, expected_sum, "sum of elements");
384         } else {
385             assert_eq!(
386                 res,
387                 types::Errno::InvalidArg as i32,
388                 "expected error out-of-bounds sum_of_elements"
389             );
390         }
391 
392         // Index an array off the end of the array:
393         let res = array_traversal::sum_of_elements(
394             &mut ctx,
395             &mut memory,
396             self.element_loc.ptr as i32,
397             self.elements.len() as i32,
398             self.start_ix as i32,
399             self.elements.len() as i32 + 1,
400             self.return_loc.ptr as i32,
401         )
402         .unwrap();
403         assert_eq!(
404             res,
405             types::Errno::InvalidArg as i32,
406             "out of bounds sum_of_elements errno"
407         );
408     }
409 }
410 proptest! {
411     #[test]
412     fn sum_elements(e in SumElementsExercise::strat()) {
413         e.test()
414     }
415 }
416