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