Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/wasm-spec-interpreter/src/with_library.rs
3069 views
1
//! Interpret WebAssembly modules using the OCaml spec interpreter.
2
//!
3
//! ```
4
//! # use wasm_spec_interpreter::{SpecValue, interpret, instantiate};
5
//! let module = wat::parse_file("tests/add.wat").unwrap();
6
//! let instance = instantiate(&module).unwrap();
7
//! let parameters = vec![SpecValue::I32(42), SpecValue::I32(1)];
8
//! let results = interpret(&instance, "add", Some(parameters)).unwrap();
9
//! assert_eq!(results, &[SpecValue::I32(43)]);
10
//! ```
11
//!
12
//! ### Warning
13
//!
14
//! The OCaml runtime is [not re-entrant]. The code below must ensure that only
15
//! one Rust thread is executing at a time (using the `INTERPRET` lock) or we
16
//! may observe `SIGSEGV` failures, e.g., while running `cargo test`.
17
//!
18
//! [not re-entrant]:
19
//! https://ocaml.org/manual/intfc.html#ss:parallel-execution-long-running-c-code
20
//!
21
//! ### Warning
22
//!
23
//! This module uses an unsafe approach (`OCamlRuntime::init_persistent()` +
24
//! `OCamlRuntime::recover_handle()`) to initializing the `OCamlRuntime` based
25
//! on some [discussion] with `ocaml-interop` crate authors. This approach was
26
//! their recommendation to resolve seeing errors like `boxroot is not setup`
27
//! followed by a `SIGSEGV`; this is similar to the testing approach [they use].
28
//! Use this approach with care and note that it is only as safe as the OCaml
29
//! code running underneath.
30
//!
31
//! [discussion]: https://github.com/tezedge/ocaml-interop/issues/35
32
//! [they use]:
33
//! https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/src/lib.rs
34
35
use crate::{SpecExport, SpecInstance, SpecValue};
36
use ocaml_interop::{BoxRoot, OCamlRuntime, ToOCaml};
37
use std::sync::Mutex;
38
39
static INTERPRET: Mutex<()> = Mutex::new(());
40
41
/// Instantiate the WebAssembly module in the spec interpreter.
42
pub fn instantiate(module: &[u8]) -> Result<SpecInstance, String> {
43
let _lock = INTERPRET.lock().unwrap();
44
OCamlRuntime::init_persistent();
45
let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
46
47
let module = module.to_boxroot(ocaml_runtime);
48
let instance = ocaml_bindings::instantiate(ocaml_runtime, &module);
49
instance.to_rust(ocaml_runtime)
50
}
51
52
/// Interpret the exported function `name` with the given `parameters`.
53
pub fn interpret(
54
instance: &SpecInstance,
55
name: &str,
56
parameters: Option<Vec<SpecValue>>,
57
) -> Result<Vec<SpecValue>, String> {
58
let _lock = INTERPRET.lock().unwrap();
59
OCamlRuntime::init_persistent();
60
let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
61
62
// Prepare the box-rooted parameters.
63
let instance = instance.to_boxroot(ocaml_runtime);
64
let name = name.to_string().to_boxroot(ocaml_runtime);
65
let parameters = parameters.to_boxroot(ocaml_runtime);
66
67
// Interpret the function.
68
let results = ocaml_bindings::interpret(ocaml_runtime, &instance, &name, &parameters);
69
results.to_rust(&ocaml_runtime)
70
}
71
72
/// Interpret the first function in the passed WebAssembly module (in Wasm form,
73
/// currently, not WAT), optionally with the given parameters. If no parameters
74
/// are provided, the function is invoked with zeroed parameters.
75
pub fn interpret_legacy(
76
module: &[u8],
77
opt_parameters: Option<Vec<SpecValue>>,
78
) -> Result<Vec<SpecValue>, String> {
79
let _lock = INTERPRET.lock().unwrap();
80
OCamlRuntime::init_persistent();
81
let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
82
83
// Parse and execute, returning results converted to Rust.
84
let module = module.to_boxroot(ocaml_runtime);
85
let opt_parameters = opt_parameters.to_boxroot(ocaml_runtime);
86
let results = ocaml_bindings::interpret_legacy(ocaml_runtime, &module, &opt_parameters);
87
results.to_rust(ocaml_runtime)
88
}
89
90
/// Retrieve the export given by `name`.
91
pub fn export(instance: &SpecInstance, name: &str) -> Result<SpecExport, String> {
92
let _lock = INTERPRET.lock().unwrap();
93
OCamlRuntime::init_persistent();
94
let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
95
96
// Prepare the box-rooted parameters.
97
let instance = instance.to_boxroot(ocaml_runtime);
98
let name = name.to_string().to_boxroot(ocaml_runtime);
99
100
// Export the value.
101
let results = ocaml_bindings::export(ocaml_runtime, &instance, &name);
102
results.to_rust(&ocaml_runtime)
103
}
104
105
// Here we declare which functions we will use from the OCaml library. See
106
// https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/index.html#example.
107
mod ocaml_bindings {
108
use super::*;
109
use ocaml_interop::{
110
FromOCaml, OCaml, OCamlBytes, OCamlInt32, OCamlInt64, OCamlList, impl_conv_ocaml_variant,
111
ocaml,
112
};
113
114
// Using this macro converts the enum both ways: Rust to OCaml and OCaml to
115
// Rust. See
116
// https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/macro.impl_conv_ocaml_variant.html.
117
impl_conv_ocaml_variant! {
118
SpecValue {
119
SpecValue::I32(i: OCamlInt32),
120
SpecValue::I64(i: OCamlInt64),
121
SpecValue::F32(i: OCamlInt32),
122
SpecValue::F64(i: OCamlInt64),
123
SpecValue::V128(i: OCamlBytes),
124
}
125
}
126
127
// We need to also convert the `SpecExport` enum.
128
impl_conv_ocaml_variant! {
129
SpecExport {
130
SpecExport::Global(i: SpecValue),
131
SpecExport::Memory(i: OCamlBytes),
132
}
133
}
134
135
// We manually show `SpecInstance` how to convert itself to and from OCaml.
136
unsafe impl FromOCaml<SpecInstance> for SpecInstance {
137
fn from_ocaml(v: OCaml<SpecInstance>) -> Self {
138
Self {
139
repr: BoxRoot::new(v),
140
}
141
}
142
}
143
unsafe impl ToOCaml<SpecInstance> for SpecInstance {
144
fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, SpecInstance> {
145
BoxRoot::get(&self.repr, cr)
146
}
147
}
148
149
// These functions must be exposed from OCaml with:
150
// `Callback.register "interpret" interpret`
151
//
152
// In Rust, these functions look like:
153
// `pub fn interpret(_: &mut OCamlRuntime, ...: OCamlRef<...>) -> BoxRoot<...>;`
154
//
155
// The `ocaml!` macro does not understand documentation, so the
156
// documentation is included here:
157
// - `instantiate`: clear the global store and instantiate a new WebAssembly
158
// module from bytes
159
// - `interpret`: given an instance, call the function exported at `name`
160
// - `interpret_legacy`: starting from bytes, instantiate and execute the
161
// first exported function
162
// - `export`: given an instance, get the value of the export at `name`
163
ocaml! {
164
pub fn instantiate(module: OCamlBytes) -> Result<SpecInstance, String>;
165
pub fn interpret(instance: SpecInstance, name: String, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;
166
pub fn interpret_legacy(module: OCamlBytes, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;
167
pub fn export(instance: SpecInstance, name: String) -> Result<SpecExport, String>;
168
}
169
}
170
171
/// Initialize a persistent OCaml runtime.
172
///
173
/// When used for fuzzing differentially with engines that also use signal
174
/// handlers, this function provides a way to explicitly set up the OCaml
175
/// runtime and configure its signal handlers.
176
pub fn setup_ocaml_runtime() {
177
let _lock = INTERPRET.lock().unwrap();
178
OCamlRuntime::init_persistent();
179
}
180
181
#[cfg(test)]
182
mod tests {
183
use super::*;
184
185
#[test]
186
fn invalid_function_name() {
187
let module = wat::parse_file("tests/add.wat").unwrap();
188
let instance = instantiate(&module).unwrap();
189
let results = interpret(
190
&instance,
191
"not-the-right-name",
192
Some(vec![SpecValue::I32(0), SpecValue::I32(0)]),
193
);
194
assert_eq!(results, Err("Not_found".to_string()));
195
}
196
197
#[test]
198
fn multiple_invocation() {
199
let module = wat::parse_file("tests/add.wat").unwrap();
200
let instance = instantiate(&module).unwrap();
201
202
let results1 = interpret(
203
&instance,
204
"add",
205
Some(vec![SpecValue::I32(42), SpecValue::I32(1)]),
206
)
207
.unwrap();
208
let results2 = interpret(
209
&instance,
210
"add",
211
Some(vec![SpecValue::I32(1), SpecValue::I32(42)]),
212
)
213
.unwrap();
214
assert_eq!(results1, results2);
215
216
let results3 = interpret(
217
&instance,
218
"add",
219
Some(vec![SpecValue::I32(20), SpecValue::I32(23)]),
220
)
221
.unwrap();
222
assert_eq!(results2, results3);
223
}
224
225
#[test]
226
fn multiple_invocation_legacy() {
227
let module = wat::parse_file("tests/add.wat").unwrap();
228
229
let results1 =
230
interpret_legacy(&module, Some(vec![SpecValue::I32(42), SpecValue::I32(1)])).unwrap();
231
let results2 =
232
interpret_legacy(&module, Some(vec![SpecValue::I32(1), SpecValue::I32(42)])).unwrap();
233
assert_eq!(results1, results2);
234
235
let results3 =
236
interpret_legacy(&module, Some(vec![SpecValue::I32(20), SpecValue::I32(23)])).unwrap();
237
assert_eq!(results2, results3);
238
}
239
240
#[test]
241
fn oob() {
242
let module = wat::parse_file("tests/oob.wat").unwrap();
243
let instance = instantiate(&module).unwrap();
244
let results = interpret(&instance, "oob", None);
245
assert_eq!(
246
results,
247
Err("Error(_, \"(Isabelle) trap: load\")".to_string())
248
);
249
}
250
251
#[test]
252
fn oob_legacy() {
253
let module = wat::parse_file("tests/oob.wat").unwrap();
254
let results = interpret_legacy(&module, None);
255
assert_eq!(
256
results,
257
Err("Error(_, \"(Isabelle) trap: load\")".to_string())
258
);
259
}
260
261
#[test]
262
fn simd_not() {
263
let module = wat::parse_file("tests/simd_not.wat").unwrap();
264
let instance = instantiate(&module).unwrap();
265
266
let parameters = Some(vec![SpecValue::V128(vec![
267
0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,
268
])]);
269
let results = interpret(&instance, "simd_not", parameters).unwrap();
270
271
assert_eq!(
272
results,
273
vec![SpecValue::V128(vec![
274
255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255
275
])]
276
);
277
}
278
279
#[test]
280
fn simd_not_legacy() {
281
let module = wat::parse_file("tests/simd_not.wat").unwrap();
282
283
let parameters = Some(vec![SpecValue::V128(vec![
284
0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,
285
])]);
286
let results = interpret_legacy(&module, parameters).unwrap();
287
288
assert_eq!(
289
results,
290
vec![SpecValue::V128(vec![
291
255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255
292
])]
293
);
294
}
295
296
// See issue https://github.com/bytecodealliance/wasmtime/issues/4671.
297
#[test]
298
fn order_of_params() {
299
let module = wat::parse_file("tests/shr_s.wat").unwrap();
300
let instance = instantiate(&module).unwrap();
301
302
let parameters = Some(vec![
303
SpecValue::I32(1795123818),
304
SpecValue::I32(-2147483648),
305
]);
306
let results = interpret(&instance, "test", parameters).unwrap();
307
308
assert_eq!(results, vec![SpecValue::I32(1795123818)]);
309
}
310
311
// See issue https://github.com/bytecodealliance/wasmtime/issues/4671.
312
#[test]
313
fn order_of_params_legacy() {
314
let module = wat::parse_file("tests/shr_s.wat").unwrap();
315
316
let parameters = Some(vec![
317
SpecValue::I32(1795123818),
318
SpecValue::I32(-2147483648),
319
]);
320
let results = interpret_legacy(&module, parameters).unwrap();
321
322
assert_eq!(results, vec![SpecValue::I32(1795123818)]);
323
}
324
325
#[test]
326
fn load_store_and_export() {
327
let module = wat::parse_file("tests/memory.wat").unwrap();
328
let instance = instantiate(&module).unwrap();
329
330
// Store 42 at offset 4.
331
let _ = interpret(
332
&instance,
333
"store_i32",
334
Some(vec![SpecValue::I32(4), SpecValue::I32(42)]),
335
);
336
337
// Load an i32 from offset 4.
338
let loaded = interpret(&instance, "load_i32", Some(vec![SpecValue::I32(4)]));
339
340
// Check stored value was retrieved.
341
assert_eq!(loaded.unwrap(), vec![SpecValue::I32(42)]);
342
343
// Retrieve the memory exported with name "mem" and check that the
344
// 32-bit value at byte offset 4 of memory is 42.
345
let export = export(&instance, "mem");
346
match export.unwrap() {
347
SpecExport::Global(_) => panic!("incorrect export"),
348
SpecExport::Memory(m) => {
349
assert_eq!(&m[0..10], [0, 0, 0, 0, 42, 0, 0, 0, 0, 0]);
350
}
351
}
352
}
353
}
354
355