Files
wasmtime/crates/wiggle/tests/strings.rs
Andrew Brown 060f12571d wiggle: adapt Wiggle strings for shared use (#5264)
* wiggle: adapt Wiggle strings for shared use

This is an extension of #5229 for the `&str` and `&mut str` types. As
documented there, we are attempting to maintain Rust guarantees for
slices that Wiggle hands out in the presence of WebAssembly shared
memory, in which case multiple threads could be modifying the underlying
data of the slice.

This change changes the API of `GuestPtr` to return an `Option` which is
`None` when attempting to view the WebAssembly data as a string and the
underlying WebAssembly memory is shared. This reuses the
`UnsafeGuestSlice` structure from #5229 to do so and appropriately marks
the region as borrowed in Wiggle's manual borrow checker. Each original
call site in this project's WASI implementations is fixed up to `expect`
that a non-shared memory is used.  (Note that I can find no uses of
`GuestStrMut` in the WASI implementations).

* wiggle: make `GuestStr*` containers wrappers of `GuestSlice*`

This change makes it possible to reuse the underlying logic in
`UnsafeGuestSlice` and the `GuestSlice*` implementations to continue to
expose the `GuestStr` and `GuestStrMut` types. These types now are
simple wrappers of their `GuestSlice*` variant. The UTF-8 validation
that distinguished `GuestStr*` now lives in the `TryFrom`
implementations for each type.
2022-11-14 22:33:24 +00:00

323 lines
9.8 KiB
Rust

use proptest::prelude::*;
use wiggle::{GuestMemory, GuestPtr};
use wiggle_test::{impl_errno, HostMemory, MemArea, MemAreas, WasiCtx};
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/strings.witx"],
});
impl_errno!(types::Errno);
impl<'a> strings::Strings for WasiCtx<'a> {
fn hello_string(&mut self, a_string: &GuestPtr<str>) -> Result<u32, types::Errno> {
let s = a_string
.as_str()
.expect("should be valid string")
.expect("expected non-shared memory");
println!("a_string='{}'", &*s);
Ok(s.len() as u32)
}
fn multi_string(
&mut self,
a: &GuestPtr<str>,
b: &GuestPtr<str>,
c: &GuestPtr<str>,
) -> Result<u32, types::Errno> {
let sa = a
.as_str()
.expect("A should be valid string")
.expect("expected non-shared memory");
let sb = b
.as_str()
.expect("B should be valid string")
.expect("expected non-shared memory");
let sc = c
.as_str()
.expect("C should be valid string")
.expect("expected non-shared memory");
let total_len = sa.len() + sb.len() + sc.len();
println!(
"len={}, a='{}', b='{}', c='{}'",
total_len, &*sa, &*sb, &*sc
);
Ok(total_len as u32)
}
}
fn unicode_string_strategy() -> impl Strategy<Value = String> {
"\\p{Greek}{1,256}"
}
fn ascii_string_strategy() -> impl Strategy<Value = String> {
"[a-zA-Z0..9]{1,256}"
}
#[derive(Debug)]
struct HelloStringExercise {
test_word: String,
string_ptr_loc: MemArea,
return_ptr_loc: MemArea,
}
impl HelloStringExercise {
pub fn strat() -> BoxedStrategy<Self> {
(unicode_string_strategy(),)
.prop_flat_map(|(test_word,)| {
(
Just(test_word.clone()),
HostMemory::mem_area_strat(test_word.len() as u32),
HostMemory::mem_area_strat(4),
)
})
.prop_map(|(test_word, string_ptr_loc, return_ptr_loc)| Self {
test_word,
string_ptr_loc,
return_ptr_loc,
})
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.string_ptr_loc, e.return_ptr_loc])
})
.boxed()
}
pub fn test(&self) {
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
// Populate string in guest's memory
let ptr = host_memory.ptr::<str>((self.string_ptr_loc.ptr, self.test_word.len() as u32));
for (slot, byte) in ptr.as_bytes().iter().zip(self.test_word.bytes()) {
slot.expect("should be valid pointer")
.write(byte)
.expect("failed to write");
}
let res = strings::hello_string(
&mut ctx,
&host_memory,
self.string_ptr_loc.ptr as i32,
self.test_word.len() as i32,
self.return_ptr_loc.ptr as i32,
)
.unwrap();
assert_eq!(res, types::Errno::Ok as i32, "hello string errno");
let given = host_memory
.ptr::<u32>(self.return_ptr_loc.ptr)
.read()
.expect("deref ptr to return value");
assert_eq!(self.test_word.len() as u32, given);
}
}
proptest! {
#[test]
fn hello_string(e in HelloStringExercise::strat()) {
e.test()
}
}
#[derive(Debug)]
struct MultiStringExercise {
a: String,
b: String,
c: String,
sa_ptr_loc: MemArea,
sb_ptr_loc: MemArea,
sc_ptr_loc: MemArea,
return_ptr_loc: MemArea,
}
impl MultiStringExercise {
pub fn strat() -> BoxedStrategy<Self> {
(
unicode_string_strategy(),
unicode_string_strategy(),
unicode_string_strategy(),
HostMemory::mem_area_strat(4),
)
.prop_flat_map(|(a, b, c, return_ptr_loc)| {
(
Just(a.clone()),
Just(b.clone()),
Just(c.clone()),
HostMemory::byte_slice_strat(
a.len() as u32,
1,
&MemAreas::from([return_ptr_loc]),
),
Just(return_ptr_loc),
)
})
.prop_flat_map(|(a, b, c, sa_ptr_loc, return_ptr_loc)| {
(
Just(a.clone()),
Just(b.clone()),
Just(c.clone()),
Just(sa_ptr_loc),
HostMemory::byte_slice_strat(
b.len() as u32,
1,
&MemAreas::from([sa_ptr_loc, return_ptr_loc]),
),
Just(return_ptr_loc),
)
})
.prop_flat_map(|(a, b, c, sa_ptr_loc, sb_ptr_loc, return_ptr_loc)| {
(
Just(a.clone()),
Just(b.clone()),
Just(c.clone()),
Just(sa_ptr_loc),
Just(sb_ptr_loc),
HostMemory::byte_slice_strat(
c.len() as u32,
1,
&MemAreas::from([sa_ptr_loc, sb_ptr_loc, return_ptr_loc]),
),
Just(return_ptr_loc),
)
})
.prop_map(
|(a, b, c, sa_ptr_loc, sb_ptr_loc, sc_ptr_loc, return_ptr_loc)| {
MultiStringExercise {
a,
b,
c,
sa_ptr_loc,
sb_ptr_loc,
sc_ptr_loc,
return_ptr_loc,
}
},
)
.boxed()
}
pub fn test(&self) {
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let write_string = |val: &str, loc: MemArea| {
let ptr = host_memory.ptr::<str>((loc.ptr, val.len() as u32));
for (slot, byte) in ptr.as_bytes().iter().zip(val.bytes()) {
slot.expect("should be valid pointer")
.write(byte)
.expect("failed to write");
}
};
write_string(&self.a, self.sa_ptr_loc);
write_string(&self.b, self.sb_ptr_loc);
write_string(&self.c, self.sc_ptr_loc);
let res = strings::multi_string(
&mut ctx,
&host_memory,
self.sa_ptr_loc.ptr as i32,
self.a.len() as i32,
self.sb_ptr_loc.ptr as i32,
self.b.len() as i32,
self.sc_ptr_loc.ptr as i32,
self.c.len() as i32,
self.return_ptr_loc.ptr as i32,
)
.unwrap();
assert_eq!(res, types::Errno::Ok as i32, "multi string errno");
let given = host_memory
.ptr::<u32>(self.return_ptr_loc.ptr)
.read()
.expect("deref ptr to return value");
assert_eq!((self.a.len() + self.b.len() + self.c.len()) as u32, given);
}
}
proptest! {
#[test]
fn multi_string(e in MultiStringExercise::strat()) {
e.test()
}
}
#[derive(Debug)]
struct OverlappingStringExercise {
a: String,
sa_ptr_loc: MemArea,
offset_b: u32,
offset_c: u32,
return_ptr_loc: MemArea,
}
impl OverlappingStringExercise {
pub fn strat() -> BoxedStrategy<Self> {
// using ascii so we can window into it without worrying about codepoints
(ascii_string_strategy(), HostMemory::mem_area_strat(4))
.prop_flat_map(|(a, return_ptr_loc)| {
(
Just(a.clone()),
HostMemory::mem_area_strat(a.len() as u32),
0..(a.len() as u32),
0..(a.len() as u32),
Just(return_ptr_loc),
)
})
.prop_map(|(a, sa_ptr_loc, offset_b, offset_c, return_ptr_loc)| Self {
a,
sa_ptr_loc,
offset_b,
offset_c,
return_ptr_loc,
})
.prop_filter("non-overlapping pointers", |e| {
MemArea::non_overlapping_set(&[e.sa_ptr_loc, e.return_ptr_loc])
})
.boxed()
}
pub fn test(&self) {
let mut ctx = WasiCtx::new();
let host_memory = HostMemory::new();
let write_string = |val: &str, loc: MemArea| {
let ptr = host_memory.ptr::<str>((loc.ptr, val.len() as u32));
for (slot, byte) in ptr.as_bytes().iter().zip(val.bytes()) {
slot.expect("should be valid pointer")
.write(byte)
.expect("failed to write");
}
};
write_string(&self.a, self.sa_ptr_loc);
let a_len = self.a.as_bytes().len() as i32;
let res = strings::multi_string(
&mut ctx,
&host_memory,
self.sa_ptr_loc.ptr as i32,
a_len,
(self.sa_ptr_loc.ptr + self.offset_b) as i32,
a_len - self.offset_b as i32,
(self.sa_ptr_loc.ptr + self.offset_c) as i32,
a_len - self.offset_c as i32,
self.return_ptr_loc.ptr as i32,
)
.unwrap();
assert_eq!(res, types::Errno::Ok as i32, "multi string errno");
let given = host_memory
.ptr::<u32>(self.return_ptr_loc.ptr)
.read()
.expect("deref ptr to return value");
assert_eq!(
((3 * a_len) - (self.offset_b as i32 + self.offset_c as i32)) as u32,
given
);
}
}
proptest! {
#[test]
fn overlapping_string(e in OverlappingStringExercise::strat()) {
e.test()
}
}