Skip to content

Commit 5fb6a94

Browse files
authored
Add table-related API coverage to the public API fuzzer (#13440)
1 parent 77d3a20 commit 5fb6a94

2 files changed

Lines changed: 281 additions & 0 deletions

File tree

crates/fuzzing/src/generators/api.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ struct Swarm {
5858
global_ty: bool,
5959
global_drop: bool,
6060
get_global_export: bool,
61+
table_type_new: bool,
62+
table_type_drop: bool,
63+
table_new: bool,
64+
table_get: bool,
65+
table_set: bool,
66+
table_grow: bool,
67+
table_size: bool,
68+
table_ty: bool,
69+
table_drop: bool,
70+
get_table_export: bool,
6171
}
6272

6373
/// A call to one of Wasmtime's public APIs.
@@ -120,6 +130,44 @@ pub enum ApiCall {
120130
instance: usize,
121131
nth: usize,
122132
},
133+
TableTypeNew {
134+
id: usize,
135+
nullable: bool,
136+
},
137+
TableTypeDrop {
138+
id: usize,
139+
},
140+
TableNew {
141+
id: usize,
142+
table_ty: usize,
143+
store: usize,
144+
},
145+
TableGet {
146+
table: usize,
147+
idx: u64,
148+
},
149+
TableSet {
150+
table: usize,
151+
idx: u64,
152+
},
153+
TableGrow {
154+
table: usize,
155+
delta: u32,
156+
},
157+
TableSize {
158+
table: usize,
159+
},
160+
TableTy {
161+
table: usize,
162+
},
163+
TableDrop {
164+
id: usize,
165+
},
166+
GetTableExport {
167+
id: usize,
168+
instance: usize,
169+
nth: usize,
170+
},
123171
}
124172
use ApiCall::*;
125173

@@ -143,6 +191,13 @@ struct Scope {
143191
/// associated `store_id`.
144192
globals: BTreeMap<usize, usize>, // global_id -> store_id
145193

194+
/// Table types that are currently live.
195+
table_types: BTreeSet<usize>,
196+
197+
/// Tables that are currently live. Maps from `table_id` to the table's
198+
/// associated `store_id`.
199+
tables: BTreeMap<usize, usize>, // table_id -> store_id
200+
146201
config: Config,
147202
}
148203

@@ -176,6 +231,8 @@ impl<'a> Arbitrary<'a> for ApiCalls {
176231
instances: BTreeMap::default(),
177232
global_types: BTreeSet::default(),
178233
globals: BTreeMap::default(),
234+
table_types: BTreeSet::default(),
235+
tables: BTreeMap::default(),
179236
config: config.clone(),
180237
};
181238

@@ -213,6 +270,7 @@ impl<'a> Arbitrary<'a> for ApiCalls {
213270
scope.stores.remove(&id);
214271
scope.instances.retain(|_, store_id| *store_id != id);
215272
scope.globals.retain(|_, store_id| *store_id != id);
273+
scope.tables.retain(|_, store_id| *store_id != id);
216274
Ok(StoreDrop { id })
217275
});
218276
}
@@ -334,6 +392,94 @@ impl<'a> Arbitrary<'a> for ApiCalls {
334392
Ok(GetGlobalExport { id, instance, nth })
335393
});
336394
}
395+
if swarm.table_type_new {
396+
choices.push(|input, scope| {
397+
let id = scope.next_id();
398+
let nullable = bool::arbitrary(input)?;
399+
scope.table_types.insert(id);
400+
Ok(TableTypeNew { id, nullable })
401+
});
402+
}
403+
if swarm.table_type_drop && !scope.table_types.is_empty() {
404+
choices.push(|input, scope| {
405+
let types: Vec<_> = scope.table_types.iter().collect();
406+
let id = **input.choose(&types)?;
407+
scope.table_types.remove(&id);
408+
Ok(TableTypeDrop { id })
409+
});
410+
}
411+
if swarm.table_new && !scope.table_types.is_empty() && !scope.stores.is_empty() {
412+
choices.push(|input, scope| {
413+
let types: Vec<_> = scope.table_types.iter().collect();
414+
let table_ty = **input.choose(&types)?;
415+
let stores: Vec<_> = scope.stores.iter().collect();
416+
let store = **input.choose(&stores)?;
417+
let id = scope.next_id();
418+
scope.tables.insert(id, store);
419+
Ok(TableNew {
420+
id,
421+
table_ty,
422+
store,
423+
})
424+
});
425+
}
426+
if swarm.table_get && !scope.tables.is_empty() {
427+
choices.push(|input, scope| {
428+
let tables: Vec<_> = scope.tables.keys().collect();
429+
let table = **input.choose(&tables)?;
430+
let idx = u64::arbitrary(input)?;
431+
Ok(TableGet { table, idx })
432+
});
433+
}
434+
if swarm.table_set && !scope.tables.is_empty() {
435+
choices.push(|input, scope| {
436+
let tables: Vec<_> = scope.tables.keys().collect();
437+
let table = **input.choose(&tables)?;
438+
let idx = u64::arbitrary(input)?;
439+
Ok(TableSet { table, idx })
440+
});
441+
}
442+
if swarm.table_grow && !scope.tables.is_empty() {
443+
choices.push(|input, scope| {
444+
let tables: Vec<_> = scope.tables.keys().collect();
445+
let table = **input.choose(&tables)?;
446+
let delta = u32::arbitrary(input)?;
447+
Ok(TableGrow { table, delta })
448+
});
449+
}
450+
if swarm.table_size && !scope.tables.is_empty() {
451+
choices.push(|input, scope| {
452+
let tables: Vec<_> = scope.tables.keys().collect();
453+
let table = **input.choose(&tables)?;
454+
Ok(TableSize { table })
455+
});
456+
}
457+
if swarm.table_ty && !scope.tables.is_empty() {
458+
choices.push(|input, scope| {
459+
let tables: Vec<_> = scope.tables.keys().collect();
460+
let table = **input.choose(&tables)?;
461+
Ok(TableTy { table })
462+
});
463+
}
464+
if swarm.table_drop && !scope.tables.is_empty() {
465+
choices.push(|input, scope| {
466+
let tables: Vec<_> = scope.tables.keys().collect();
467+
let id = **input.choose(&tables)?;
468+
scope.tables.remove(&id);
469+
Ok(TableDrop { id })
470+
});
471+
}
472+
if swarm.get_table_export && !scope.instances.is_empty() {
473+
choices.push(|input, scope| {
474+
let instances: Vec<_> = scope.instances.keys().collect();
475+
let instance = **input.choose(&instances)?;
476+
let nth = usize::arbitrary(input)?;
477+
let id = scope.next_id();
478+
let store = *scope.instances.get(&instance).unwrap();
479+
scope.tables.insert(id, store);
480+
Ok(GetTableExport { id, instance, nth })
481+
});
482+
}
337483

338484
if choices.is_empty() {
339485
break;

crates/fuzzing/src/oracles/api.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub fn make_api_calls(api: ApiCalls) {
1717
let mut instances: HashMap<usize, (Instance, usize)> = Default::default();
1818
let mut global_types: HashMap<usize, GlobalType> = Default::default();
1919
let mut globals: HashMap<usize, (Global, usize)> = Default::default();
20+
let mut table_types: HashMap<usize, TableType> = Default::default();
21+
let mut tables: HashMap<usize, (Table, usize)> = Default::default();
2022

2123
for call in api.calls {
2224
match call {
@@ -32,6 +34,7 @@ pub fn make_api_calls(api: ApiCalls) {
3234
log::trace!("dropping store {id}");
3335
instances.retain(|_, (_, store_id)| *store_id != id);
3436
globals.retain(|_, (_, store_id)| *store_id != id);
37+
tables.retain(|_, (_, store_id)| *store_id != id);
3538
stores.remove(&id);
3639
}
3740

@@ -223,6 +226,138 @@ pub fn make_api_calls(api: ApiCalls) {
223226
}
224227
globals.insert(id, (gs[nth % gs.len()], store_id));
225228
}
229+
230+
ApiCall::TableTypeNew { id, nullable } => {
231+
log::trace!("creating table type {id}");
232+
let element = RefType::new(nullable, HeapType::Func);
233+
let old = table_types.insert(id, TableType::new(element, 0, None));
234+
assert!(old.is_none());
235+
}
236+
237+
ApiCall::TableTypeDrop { id } => {
238+
log::trace!("dropping table type {id}");
239+
table_types.remove(&id);
240+
}
241+
242+
ApiCall::TableNew {
243+
id,
244+
table_ty,
245+
store,
246+
} => {
247+
log::trace!("creating table {id} with type {table_ty} in store {store}");
248+
let tt = match table_types.get(&table_ty) {
249+
Some(t) => t.clone(),
250+
None => continue,
251+
};
252+
let st = match stores.get_mut(&store) {
253+
Some(s) => s,
254+
None => continue,
255+
};
256+
match tt.default_value(&mut *st) {
257+
Ok(t) => {
258+
tables.insert(id, (t, store));
259+
}
260+
Err(_) => continue,
261+
}
262+
}
263+
264+
ApiCall::TableGet { table, idx } => {
265+
log::trace!("getting table {table} at index {idx}");
266+
let (t, store_id) = match tables.get(&table) {
267+
Some(&x) => x,
268+
None => continue,
269+
};
270+
let st = match stores.get_mut(&store_id) {
271+
Some(s) => s,
272+
None => continue,
273+
};
274+
let _ = t.get(&mut *st, idx);
275+
}
276+
277+
ApiCall::TableSet { table, idx } => {
278+
log::trace!("setting table {table} at index {idx}");
279+
let (t, store_id) = match tables.get(&table) {
280+
Some(&x) => x,
281+
None => continue,
282+
};
283+
let st = match stores.get_mut(&store_id) {
284+
Some(s) => s,
285+
None => continue,
286+
};
287+
let ty = t.ty(&*st);
288+
let val: ValType = ty.element().clone().into();
289+
if let Some(init) = val.default_value() {
290+
let _ = t.set(&mut *st, idx, init.ref_().unwrap());
291+
}
292+
}
293+
294+
ApiCall::TableGrow { table, delta } => {
295+
log::trace!("growing table {table} by {delta}");
296+
let (t, store_id) = match tables.get(&table) {
297+
Some(&x) => x,
298+
None => continue,
299+
};
300+
let st = match stores.get_mut(&store_id) {
301+
Some(s) => s,
302+
None => continue,
303+
};
304+
let ty = t.ty(&*st);
305+
let val: ValType = ty.element().clone().into();
306+
if let Some(init) = val.default_value() {
307+
let _ = t.grow(&mut *st, delta.into(), init.ref_().unwrap());
308+
}
309+
}
310+
311+
ApiCall::TableSize { table } => {
312+
log::trace!("getting size of table {table}");
313+
let (t, store_id) = match tables.get(&table) {
314+
Some(&x) => x,
315+
None => continue,
316+
};
317+
let st = match stores.get(&store_id) {
318+
Some(s) => s,
319+
None => continue,
320+
};
321+
let _ = t.size(st);
322+
}
323+
324+
ApiCall::TableTy { table } => {
325+
log::trace!("checking type of table {table}");
326+
let (t, store_id) = match tables.get(&table) {
327+
Some(&x) => x,
328+
None => continue,
329+
};
330+
let st = match stores.get(&store_id) {
331+
Some(s) => s,
332+
None => continue,
333+
};
334+
let _ = t.ty(st);
335+
}
336+
337+
ApiCall::TableDrop { id } => {
338+
log::trace!("dropping table {id}");
339+
tables.remove(&id);
340+
}
341+
342+
ApiCall::GetTableExport { id, instance, nth } => {
343+
log::trace!("getting {nth}th table export of instance {instance} as {id}");
344+
let (inst, store_id) = match instances.get(&instance) {
345+
Some(&x) => x,
346+
None => continue,
347+
};
348+
let st = match stores.get_mut(&store_id) {
349+
Some(s) => s,
350+
None => continue,
351+
};
352+
let ts = inst
353+
.exports(&mut *st)
354+
.filter_map(|e| e.into_table())
355+
.collect::<Vec<_>>();
356+
if ts.is_empty() {
357+
continue;
358+
}
359+
tables.insert(id, (ts[nth % ts.len()], store_id));
360+
}
226361
}
227362
}
228363
}

0 commit comments

Comments
 (0)