diff --git a/crates/fuzzing/src/generators/api.rs b/crates/fuzzing/src/generators/api.rs index f9cbc070ea50..e84694d7744b 100644 --- a/crates/fuzzing/src/generators/api.rs +++ b/crates/fuzzing/src/generators/api.rs @@ -68,6 +68,21 @@ struct Swarm { table_ty: bool, table_drop: bool, get_table_export: bool, + memory_type_new: bool, + memory_type_drop: bool, + memory_new: bool, + memory_read: bool, + memory_write: bool, + memory_data: bool, + memory_data_mut: bool, + memory_grow: bool, + memory_data_size: bool, + memory_size: bool, + memory_page_size: bool, + memory_page_size_log2: bool, + memory_ty: bool, + memory_drop: bool, + get_memory_export: bool, } /// A call to one of Wasmtime's public APIs. @@ -168,6 +183,62 @@ pub enum ApiCall { instance: usize, nth: usize, }, + MemoryTypeNew { + id: usize, + minimum: u32, + maximum: Option, + }, + MemoryTypeDrop { + id: usize, + }, + MemoryNew { + id: usize, + memory_ty: usize, + store: usize, + }, + MemoryRead { + memory: usize, + offset: usize, + len: usize, + }, + MemoryWrite { + memory: usize, + offset: usize, + data: Vec, + }, + MemoryData { + memory: usize, + }, + MemoryDataMut { + memory: usize, + }, + MemoryGrow { + memory: usize, + delta: u32, + }, + MemoryDataSize { + memory: usize, + }, + MemorySize { + memory: usize, + }, + MemoryPageSize { + memory: usize, + }, + MemoryPageSizeLog2 { + memory: usize, + }, + MemoryTy { + memory: usize, + }, + MemoryDrop { + id: usize, + }, + GetMemoryExport { + id: usize, + instance: usize, + nth: usize, + }, } use ApiCall::*; @@ -198,6 +269,13 @@ struct Scope { /// associated `store_id`. tables: BTreeMap, // table_id -> store_id + /// Memory types that are currently live. + memory_types: BTreeSet, + + /// Memories that are currently live. Maps from `memory_id` to the memory's + /// associated `store_id`. + memories: BTreeMap, // memory_id -> store_id + config: Config, } @@ -233,6 +311,8 @@ impl<'a> Arbitrary<'a> for ApiCalls { globals: BTreeMap::default(), table_types: BTreeSet::default(), tables: BTreeMap::default(), + memory_types: BTreeSet::default(), + memories: BTreeMap::default(), config: config.clone(), }; @@ -271,6 +351,7 @@ impl<'a> Arbitrary<'a> for ApiCalls { scope.instances.retain(|_, store_id| *store_id != id); scope.globals.retain(|_, store_id| *store_id != id); scope.tables.retain(|_, store_id| *store_id != id); + scope.memories.retain(|_, store_id| *store_id != id); Ok(StoreDrop { id }) }); } @@ -480,6 +561,149 @@ impl<'a> Arbitrary<'a> for ApiCalls { Ok(GetTableExport { id, instance, nth }) }); } + if swarm.memory_type_new { + choices.push(|input, scope| { + let id = scope.next_id(); + let minimum = u32::arbitrary(input)? % 10; + let has_max = bool::arbitrary(input)?; + let maximum = if has_max { + Some(minimum + u32::arbitrary(input)? % 10) + } else { + None + }; + scope.memory_types.insert(id); + Ok(MemoryTypeNew { + id, + minimum, + maximum, + }) + }); + } + if swarm.memory_type_drop && !scope.memory_types.is_empty() { + choices.push(|input, scope| { + let types: Vec<_> = scope.memory_types.iter().collect(); + let id = **input.choose(&types)?; + scope.memory_types.remove(&id); + Ok(MemoryTypeDrop { id }) + }); + } + if swarm.memory_new && !scope.memory_types.is_empty() && !scope.stores.is_empty() { + choices.push(|input, scope| { + let types: Vec<_> = scope.memory_types.iter().collect(); + let memory_ty = **input.choose(&types)?; + let stores: Vec<_> = scope.stores.iter().collect(); + let store = **input.choose(&stores)?; + let id = scope.next_id(); + scope.memories.insert(id, store); + Ok(MemoryNew { + id, + memory_ty, + store, + }) + }); + } + if swarm.memory_read && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + let offset = usize::arbitrary(input)?; + let len = usize::arbitrary(input)? % 64; + Ok(MemoryRead { + memory, + offset, + len, + }) + }); + } + if swarm.memory_write && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + let offset = usize::arbitrary(input)?; + let data = Vec::::arbitrary(input)?; + Ok(MemoryWrite { + memory, + offset, + data, + }) + }); + } + if swarm.memory_data && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + Ok(MemoryData { memory }) + }); + } + if swarm.memory_data_mut && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + Ok(MemoryDataMut { memory }) + }); + } + if swarm.memory_grow && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + let delta = u32::arbitrary(input)? % 10; + Ok(MemoryGrow { memory, delta }) + }); + } + if swarm.memory_data_size && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + Ok(MemoryDataSize { memory }) + }); + } + if swarm.memory_size && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + Ok(MemorySize { memory }) + }); + } + if swarm.memory_page_size && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + Ok(MemoryPageSize { memory }) + }); + } + if swarm.memory_page_size_log2 && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + Ok(MemoryPageSizeLog2 { memory }) + }); + } + if swarm.memory_ty && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let memory = **input.choose(&memories)?; + Ok(MemoryTy { memory }) + }); + } + if swarm.memory_drop && !scope.memories.is_empty() { + choices.push(|input, scope| { + let memories: Vec<_> = scope.memories.keys().collect(); + let id = **input.choose(&memories)?; + scope.memories.remove(&id); + Ok(MemoryDrop { id }) + }); + } + if swarm.get_memory_export && !scope.instances.is_empty() { + choices.push(|input, scope| { + let instances: Vec<_> = scope.instances.keys().collect(); + let instance = **input.choose(&instances)?; + let nth = usize::arbitrary(input)?; + let id = scope.next_id(); + let store = *scope.instances.get(&instance).unwrap(); + scope.memories.insert(id, store); + Ok(GetMemoryExport { id, instance, nth }) + }); + } if choices.is_empty() { break; diff --git a/crates/fuzzing/src/oracles/api.rs b/crates/fuzzing/src/oracles/api.rs index 5bbfacf14ec9..45abd61ff29b 100644 --- a/crates/fuzzing/src/oracles/api.rs +++ b/crates/fuzzing/src/oracles/api.rs @@ -19,6 +19,8 @@ pub fn make_api_calls(api: ApiCalls) { let mut globals: HashMap = Default::default(); let mut table_types: HashMap = Default::default(); let mut tables: HashMap = Default::default(); + let mut memory_types: HashMap = Default::default(); + let mut memories: HashMap = Default::default(); for call in api.calls { match call { @@ -35,6 +37,7 @@ pub fn make_api_calls(api: ApiCalls) { instances.retain(|_, (_, store_id)| *store_id != id); globals.retain(|_, (_, store_id)| *store_id != id); tables.retain(|_, (_, store_id)| *store_id != id); + memories.retain(|_, (_, store_id)| *store_id != id); stores.remove(&id); } @@ -358,6 +361,210 @@ pub fn make_api_calls(api: ApiCalls) { } tables.insert(id, (ts[nth % ts.len()], store_id)); } + + ApiCall::MemoryTypeNew { + id, + minimum, + maximum, + } => { + log::trace!("creating memory type {id}"); + let old = memory_types.insert(id, MemoryType::new(minimum, maximum)); + assert!(old.is_none()); + } + + ApiCall::MemoryTypeDrop { id } => { + log::trace!("dropping memory type {id}"); + memory_types.remove(&id); + } + + ApiCall::MemoryNew { + id, + memory_ty, + store, + } => { + log::trace!("creating memory {id} with type {memory_ty} in store {store}"); + let mt = match memory_types.get(&memory_ty) { + Some(t) => t.clone(), + None => continue, + }; + let st = match stores.get_mut(&store) { + Some(s) => s, + None => continue, + }; + match Memory::new(&mut *st, mt) { + Ok(m) => { + memories.insert(id, (m, store)); + } + Err(_) => continue, + } + } + + ApiCall::MemoryRead { + memory, + offset, + len, + } => { + log::trace!("reading {len} bytes from memory {memory} at offset {offset}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get(&store_id) { + Some(s) => s, + None => continue, + }; + let mut buf = vec![0u8; len]; + let _ = m.read(st, offset, &mut buf); + } + + ApiCall::MemoryWrite { + memory, + offset, + ref data, + } => { + log::trace!( + "writing {} bytes to memory {memory} at offset {offset}", + data.len() + ); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get_mut(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.write(&mut *st, offset, data); + } + + ApiCall::MemoryData { memory } => { + log::trace!("getting data slice of memory {memory}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.data(st); + } + + ApiCall::MemoryDataMut { memory } => { + log::trace!("getting mutable data slice of memory {memory}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get_mut(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.data_mut(&mut *st); + } + + ApiCall::MemoryGrow { memory, delta } => { + log::trace!("growing memory {memory} by {delta} pages"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get_mut(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.grow(&mut *st, delta.into()); + } + + ApiCall::MemoryDataSize { memory } => { + log::trace!("getting data size of memory {memory}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.data_size(st); + } + + ApiCall::MemorySize { memory } => { + log::trace!("getting size of memory {memory}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.size(st); + } + + ApiCall::MemoryPageSize { memory } => { + log::trace!("getting page size of memory {memory}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.page_size(st); + } + + ApiCall::MemoryPageSizeLog2 { memory } => { + log::trace!("getting page size log2 of memory {memory}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.page_size_log2(st); + } + + ApiCall::MemoryTy { memory } => { + log::trace!("checking type of memory {memory}"); + let (m, store_id) = match memories.get(&memory) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get(&store_id) { + Some(s) => s, + None => continue, + }; + let _ = m.ty(st); + } + + ApiCall::MemoryDrop { id } => { + log::trace!("dropping memory {id}"); + memories.remove(&id); + } + + ApiCall::GetMemoryExport { id, instance, nth } => { + log::trace!("getting {nth}th memory export of instance {instance} as {id}"); + let (inst, store_id) = match instances.get(&instance) { + Some(&x) => x, + None => continue, + }; + let st = match stores.get_mut(&store_id) { + Some(s) => s, + None => continue, + }; + let ms = inst + .exports(&mut *st) + .filter_map(|e| e.into_memory()) + .collect::>(); + if ms.is_empty() { + continue; + } + memories.insert(id, (ms[nth % ms.len()], store_id)); + } } } }