Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/generators/api.rs
1693 views
1
//! Generating sequences of Wasmtime API calls.
2
//!
3
//! We only generate *valid* sequences of API calls. To do this, we keep track
4
//! of what objects we've already created in earlier API calls via the `Scope`
5
//! struct.
6
//!
7
//! To generate even-more-pathological sequences of API calls, we use [swarm
8
//! testing]:
9
//!
10
//! > In swarm testing, the usual practice of potentially including all features
11
//! > in every test case is abandoned. Rather, a large “swarm” of randomly
12
//! > generated configurations, each of which omits some features, is used, with
13
//! > configurations receiving equal resources.
14
//!
15
//! [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf
16
17
use crate::generators::Config;
18
use arbitrary::{Arbitrary, Unstructured};
19
use std::collections::BTreeSet;
20
21
#[derive(Arbitrary, Debug)]
22
struct Swarm {
23
module_new: bool,
24
module_drop: bool,
25
instance_new: bool,
26
instance_drop: bool,
27
call_exported_func: bool,
28
}
29
30
/// A call to one of Wasmtime's public APIs.
31
#[derive(Arbitrary, Debug)]
32
#[expect(missing_docs, reason = "self-describing fields")]
33
pub enum ApiCall {
34
StoreNew(Config),
35
ModuleNew { id: usize, wasm: Vec<u8> },
36
ModuleDrop { id: usize },
37
InstanceNew { id: usize, module: usize },
38
InstanceDrop { id: usize },
39
CallExportedFunc { instance: usize, nth: usize },
40
}
41
use ApiCall::*;
42
43
struct Scope {
44
id_counter: usize,
45
modules: BTreeSet<usize>,
46
instances: BTreeSet<usize>,
47
config: Config,
48
}
49
50
impl Scope {
51
fn next_id(&mut self) -> usize {
52
let id = self.id_counter;
53
self.id_counter = id + 1;
54
id
55
}
56
}
57
58
/// A sequence of API calls.
59
#[derive(Debug)]
60
pub struct ApiCalls {
61
/// The API calls.
62
pub calls: Vec<ApiCall>,
63
}
64
65
impl<'a> Arbitrary<'a> for ApiCalls {
66
fn arbitrary(input: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
67
crate::init_fuzzing();
68
69
let swarm = Swarm::arbitrary(input)?;
70
let mut calls = vec![];
71
72
let config = Config::arbitrary(input)?;
73
calls.push(StoreNew(config.clone()));
74
75
let mut scope = Scope {
76
id_counter: 0,
77
modules: BTreeSet::default(),
78
instances: BTreeSet::default(),
79
config,
80
};
81
82
// Total limit on number of API calls we'll generate. This exists to
83
// avoid libFuzzer timeouts.
84
let max_calls = 100;
85
86
for _ in 0..input.arbitrary_len::<ApiCall>()? {
87
if calls.len() > max_calls {
88
break;
89
}
90
91
let mut choices: Vec<fn(_, &mut Scope) -> arbitrary::Result<ApiCall>> = vec![];
92
93
if swarm.module_new {
94
choices.push(|input, scope| {
95
let id = scope.next_id();
96
let wasm = scope.config.generate(input, Some(1000))?;
97
scope.modules.insert(id);
98
Ok(ModuleNew {
99
id,
100
wasm: wasm.to_bytes(),
101
})
102
});
103
}
104
if swarm.module_drop && !scope.modules.is_empty() {
105
choices.push(|input, scope| {
106
let modules: Vec<_> = scope.modules.iter().collect();
107
let id = **input.choose(&modules)?;
108
scope.modules.remove(&id);
109
Ok(ModuleDrop { id })
110
});
111
}
112
if swarm.instance_new && !scope.modules.is_empty() {
113
choices.push(|input, scope| {
114
let modules: Vec<_> = scope.modules.iter().collect();
115
let module = **input.choose(&modules)?;
116
let id = scope.next_id();
117
scope.instances.insert(id);
118
Ok(InstanceNew { id, module })
119
});
120
}
121
if swarm.instance_drop && !scope.instances.is_empty() {
122
choices.push(|input, scope| {
123
let instances: Vec<_> = scope.instances.iter().collect();
124
let id = **input.choose(&instances)?;
125
scope.instances.remove(&id);
126
Ok(InstanceDrop { id })
127
});
128
}
129
if swarm.call_exported_func && !scope.instances.is_empty() {
130
choices.push(|input, scope| {
131
let instances: Vec<_> = scope.instances.iter().collect();
132
let instance = **input.choose(&instances)?;
133
let nth = usize::arbitrary(input)?;
134
Ok(CallExportedFunc { instance, nth })
135
});
136
}
137
138
if choices.is_empty() {
139
break;
140
}
141
let c = input.choose(&choices)?;
142
calls.push(c(input, &mut scope)?);
143
}
144
145
Ok(ApiCalls { calls })
146
}
147
}
148
149