1 use proptest::prelude::*;
2 use wiggle::{GuestMemory, GuestPtr};
3 use wiggle_test::{HostMemory, MemArea, MemAreas, WasiCtx, impl_errno};
4 
5 wiggle::from_witx!({
6     witx: ["tests/strings.witx"],
7 });
8 
9 impl_errno!(types::Errno);
10 
11 impl<'a> strings::Strings for WasiCtx<'a> {
hello_string( &mut self, memory: &mut GuestMemory<'_>, a_string: GuestPtr<str>, ) -> Result<u32, types::Errno>12     fn hello_string(
13         &mut self,
14         memory: &mut GuestMemory<'_>,
15         a_string: GuestPtr<str>,
16     ) -> Result<u32, types::Errno> {
17         let s = memory
18             .as_str(a_string)
19             .expect("should be valid string")
20             .expect("expected non-shared memory");
21         println!("a_string='{}'", &*s);
22         Ok(s.len() as u32)
23     }
24 
multi_string( &mut self, memory: &mut GuestMemory<'_>, a: GuestPtr<str>, b: GuestPtr<str>, c: GuestPtr<str>, ) -> Result<u32, types::Errno>25     fn multi_string(
26         &mut self,
27         memory: &mut GuestMemory<'_>,
28         a: GuestPtr<str>,
29         b: GuestPtr<str>,
30         c: GuestPtr<str>,
31     ) -> Result<u32, types::Errno> {
32         let sa = memory
33             .as_str(a)
34             .expect("A should be valid string")
35             .expect("expected non-shared memory");
36         let sb = memory
37             .as_str(b)
38             .expect("B should be valid string")
39             .expect("expected non-shared memory");
40         let sc = memory
41             .as_str(c)
42             .expect("C should be valid string")
43             .expect("expected non-shared memory");
44         let total_len = sa.len() + sb.len() + sc.len();
45         println!(
46             "len={}, a='{}', b='{}', c='{}'",
47             total_len, &*sa, &*sb, &*sc
48         );
49         Ok(total_len as u32)
50     }
51 }
52 
unicode_string_strategy() -> impl Strategy<Value = String>53 fn unicode_string_strategy() -> impl Strategy<Value = String> {
54     "\\p{Greek}{1,256}"
55 }
ascii_string_strategy() -> impl Strategy<Value = String>56 fn ascii_string_strategy() -> impl Strategy<Value = String> {
57     "[a-zA-Z0..9]{1,256}"
58 }
59 
60 #[derive(Debug)]
61 struct HelloStringExercise {
62     test_word: String,
63     string_ptr_loc: MemArea,
64     return_ptr_loc: MemArea,
65 }
66 
67 impl HelloStringExercise {
strat() -> BoxedStrategy<Self>68     pub fn strat() -> BoxedStrategy<Self> {
69         (unicode_string_strategy(),)
70             .prop_flat_map(|(test_word,)| {
71                 (
72                     Just(test_word.clone()),
73                     HostMemory::mem_area_strat(test_word.len() as u32),
74                     HostMemory::mem_area_strat(4),
75                 )
76             })
77             .prop_map(|(test_word, string_ptr_loc, return_ptr_loc)| Self {
78                 test_word,
79                 string_ptr_loc,
80                 return_ptr_loc,
81             })
82             .prop_filter("non-overlapping pointers", |e| {
83                 MemArea::non_overlapping_set(&[e.string_ptr_loc, e.return_ptr_loc])
84             })
85             .boxed()
86     }
87 
test(&self)88     pub fn test(&self) {
89         let mut ctx = WasiCtx::new();
90         let mut host_memory = HostMemory::new();
91         let mut memory = host_memory.guest_memory();
92 
93         // Populate string in guest's memory
94         let ptr = GuestPtr::<str>::new((self.string_ptr_loc.ptr, self.test_word.len() as u32));
95         for (slot, byte) in ptr.as_bytes().iter().zip(self.test_word.bytes()) {
96             memory
97                 .write(slot.expect("should be valid pointer"), byte)
98                 .expect("failed to write");
99         }
100 
101         let res = strings::hello_string(
102             &mut ctx,
103             &mut memory,
104             self.string_ptr_loc.ptr as i32,
105             self.test_word.len() as i32,
106             self.return_ptr_loc.ptr as i32,
107         )
108         .unwrap();
109         assert_eq!(res, types::Errno::Ok as i32, "hello string errno");
110 
111         let given = memory
112             .read(GuestPtr::<u32>::new(self.return_ptr_loc.ptr))
113             .expect("deref ptr to return value");
114         assert_eq!(self.test_word.len() as u32, given);
115     }
116 }
117 proptest! {
118     #[test]
119     fn hello_string(e in HelloStringExercise::strat()) {
120         e.test()
121     }
122 }
123 
124 #[derive(Debug)]
125 struct MultiStringExercise {
126     a: String,
127     b: String,
128     c: String,
129     sa_ptr_loc: MemArea,
130     sb_ptr_loc: MemArea,
131     sc_ptr_loc: MemArea,
132     return_ptr_loc: MemArea,
133 }
134 
135 impl MultiStringExercise {
strat() -> BoxedStrategy<Self>136     pub fn strat() -> BoxedStrategy<Self> {
137         (
138             unicode_string_strategy(),
139             unicode_string_strategy(),
140             unicode_string_strategy(),
141             HostMemory::mem_area_strat(4),
142         )
143             .prop_flat_map(|(a, b, c, return_ptr_loc)| {
144                 (
145                     Just(a.clone()),
146                     Just(b.clone()),
147                     Just(c.clone()),
148                     HostMemory::byte_slice_strat(
149                         a.len() as u32,
150                         1,
151                         &MemAreas::from([return_ptr_loc]),
152                     ),
153                     Just(return_ptr_loc),
154                 )
155             })
156             .prop_flat_map(|(a, b, c, sa_ptr_loc, return_ptr_loc)| {
157                 (
158                     Just(a.clone()),
159                     Just(b.clone()),
160                     Just(c.clone()),
161                     Just(sa_ptr_loc),
162                     HostMemory::byte_slice_strat(
163                         b.len() as u32,
164                         1,
165                         &MemAreas::from([sa_ptr_loc, return_ptr_loc]),
166                     ),
167                     Just(return_ptr_loc),
168                 )
169             })
170             .prop_flat_map(|(a, b, c, sa_ptr_loc, sb_ptr_loc, return_ptr_loc)| {
171                 (
172                     Just(a.clone()),
173                     Just(b.clone()),
174                     Just(c.clone()),
175                     Just(sa_ptr_loc),
176                     Just(sb_ptr_loc),
177                     HostMemory::byte_slice_strat(
178                         c.len() as u32,
179                         1,
180                         &MemAreas::from([sa_ptr_loc, sb_ptr_loc, return_ptr_loc]),
181                     ),
182                     Just(return_ptr_loc),
183                 )
184             })
185             .prop_map(
186                 |(a, b, c, sa_ptr_loc, sb_ptr_loc, sc_ptr_loc, return_ptr_loc)| {
187                     MultiStringExercise {
188                         a,
189                         b,
190                         c,
191                         sa_ptr_loc,
192                         sb_ptr_loc,
193                         sc_ptr_loc,
194                         return_ptr_loc,
195                     }
196                 },
197             )
198             .boxed()
199     }
200 
test(&self)201     pub fn test(&self) {
202         let mut ctx = WasiCtx::new();
203         let mut host_memory = HostMemory::new();
204         let mut memory = host_memory.guest_memory();
205 
206         let mut write_string = |val: &str, loc: MemArea| {
207             let ptr = GuestPtr::<str>::new((loc.ptr, val.len() as u32));
208             for (slot, byte) in ptr.as_bytes().iter().zip(val.bytes()) {
209                 memory
210                     .write(slot.expect("should be valid pointer"), byte)
211                     .expect("failed to write");
212             }
213         };
214 
215         write_string(&self.a, self.sa_ptr_loc);
216         write_string(&self.b, self.sb_ptr_loc);
217         write_string(&self.c, self.sc_ptr_loc);
218 
219         let res = strings::multi_string(
220             &mut ctx,
221             &mut memory,
222             self.sa_ptr_loc.ptr as i32,
223             self.a.len() as i32,
224             self.sb_ptr_loc.ptr as i32,
225             self.b.len() as i32,
226             self.sc_ptr_loc.ptr as i32,
227             self.c.len() as i32,
228             self.return_ptr_loc.ptr as i32,
229         )
230         .unwrap();
231         assert_eq!(res, types::Errno::Ok as i32, "multi string errno");
232 
233         let given = memory
234             .read(GuestPtr::<u32>::new(self.return_ptr_loc.ptr))
235             .expect("deref ptr to return value");
236         assert_eq!((self.a.len() + self.b.len() + self.c.len()) as u32, given);
237     }
238 }
239 proptest! {
240     #[test]
241     fn multi_string(e in MultiStringExercise::strat()) {
242         e.test()
243     }
244 }
245 
246 #[derive(Debug)]
247 struct OverlappingStringExercise {
248     a: String,
249     sa_ptr_loc: MemArea,
250     offset_b: u32,
251     offset_c: u32,
252     return_ptr_loc: MemArea,
253 }
254 
255 impl OverlappingStringExercise {
strat() -> BoxedStrategy<Self>256     pub fn strat() -> BoxedStrategy<Self> {
257         // using ascii so we can window into it without worrying about codepoints
258         (ascii_string_strategy(), HostMemory::mem_area_strat(4))
259             .prop_flat_map(|(a, return_ptr_loc)| {
260                 (
261                     Just(a.clone()),
262                     HostMemory::mem_area_strat(a.len() as u32),
263                     0..(a.len() as u32),
264                     0..(a.len() as u32),
265                     Just(return_ptr_loc),
266                 )
267             })
268             .prop_map(|(a, sa_ptr_loc, offset_b, offset_c, return_ptr_loc)| Self {
269                 a,
270                 sa_ptr_loc,
271                 offset_b,
272                 offset_c,
273                 return_ptr_loc,
274             })
275             .prop_filter("non-overlapping pointers", |e| {
276                 MemArea::non_overlapping_set(&[e.sa_ptr_loc, e.return_ptr_loc])
277             })
278             .boxed()
279     }
280 
test(&self)281     pub fn test(&self) {
282         let mut ctx = WasiCtx::new();
283         let mut host_memory = HostMemory::new();
284         let mut memory = host_memory.guest_memory();
285 
286         let mut write_string = |val: &str, loc: MemArea| {
287             let ptr = GuestPtr::<str>::new((loc.ptr, val.len() as u32));
288             for (slot, byte) in ptr.as_bytes().iter().zip(val.bytes()) {
289                 memory
290                     .write(slot.expect("should be valid pointer"), byte)
291                     .expect("failed to write");
292             }
293         };
294 
295         write_string(&self.a, self.sa_ptr_loc);
296 
297         let a_len = self.a.as_bytes().len() as i32;
298         let res = strings::multi_string(
299             &mut ctx,
300             &mut memory,
301             self.sa_ptr_loc.ptr as i32,
302             a_len,
303             (self.sa_ptr_loc.ptr + self.offset_b) as i32,
304             a_len - self.offset_b as i32,
305             (self.sa_ptr_loc.ptr + self.offset_c) as i32,
306             a_len - self.offset_c as i32,
307             self.return_ptr_loc.ptr as i32,
308         )
309         .unwrap();
310         assert_eq!(res, types::Errno::Ok as i32, "multi string errno");
311 
312         let given = memory
313             .read(GuestPtr::<u32>::new(self.return_ptr_loc.ptr))
314             .expect("deref ptr to return value");
315         assert_eq!(
316             ((3 * a_len) - (self.offset_b as i32 + self.offset_c as i32)) as u32,
317             given
318         );
319     }
320 }
321 
322 proptest! {
323     #[test]
324     fn overlapping_string(e in OverlappingStringExercise::strat()) {
325         e.test()
326     }
327 }
328