Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/oracles/diff_v8.rs
1693 views
1
use crate::generators::{Config, DiffValue, DiffValueType};
2
use crate::oracles::engine::{DiffEngine, DiffInstance};
3
use anyhow::{Error, Result, bail};
4
use std::cell::RefCell;
5
use std::rc::Rc;
6
use std::sync::Once;
7
use wasmtime::Trap;
8
9
pub struct V8Engine {
10
isolate: Rc<RefCell<v8::OwnedIsolate>>,
11
}
12
13
impl V8Engine {
14
pub fn new(config: &mut Config) -> V8Engine {
15
static INIT: Once = Once::new();
16
17
INIT.call_once(|| {
18
let platform = v8::new_default_platform(0, false).make_shared();
19
v8::V8::initialize_platform(platform);
20
v8::V8::initialize();
21
});
22
23
let config = &mut config.module_config.config;
24
// FIXME: reference types are disabled for now as we seemingly keep finding
25
// a segfault in v8. This is found relatively quickly locally and keeps
26
// getting found by oss-fuzz and currently we don't think that there's
27
// really much we can do about it. For the time being disable reference
28
// types entirely. An example bug is
29
// https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=45662
30
config.reference_types_enabled = false;
31
32
config.min_memories = config.min_memories.min(1);
33
config.max_memories = config.max_memories.min(1);
34
config.memory64_enabled = false;
35
config.custom_page_sizes_enabled = false;
36
config.wide_arithmetic_enabled = false;
37
38
Self {
39
isolate: Rc::new(RefCell::new(v8::Isolate::new(Default::default()))),
40
}
41
}
42
}
43
44
impl DiffEngine for V8Engine {
45
fn name(&self) -> &'static str {
46
"v8"
47
}
48
49
fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
50
// Setup a new `Context` in which we'll be creating this instance and
51
// executing code.
52
let mut isolate = self.isolate.borrow_mut();
53
let isolate = &mut **isolate;
54
let mut scope = v8::HandleScope::new(isolate);
55
let context = v8::Context::new(&mut scope, Default::default());
56
let global = context.global(&mut scope);
57
let mut scope = v8::ContextScope::new(&mut scope, context);
58
59
// Move the `wasm` into JS and then invoke `new WebAssembly.Module`.
60
let buf = v8::ArrayBuffer::new_backing_store_from_boxed_slice(wasm.into());
61
let buf = v8::SharedRef::from(buf);
62
let name = v8::String::new(&mut scope, "WASM_BINARY").unwrap();
63
let buf = v8::ArrayBuffer::with_backing_store(&mut scope, &buf);
64
global.set(&mut scope, name.into(), buf.into());
65
let module = eval(&mut scope, "new WebAssembly.Module(WASM_BINARY)").unwrap();
66
let name = v8::String::new(&mut scope, "WASM_MODULE").unwrap();
67
global.set(&mut scope, name.into(), module);
68
69
// Using our `WASM_MODULE` run instantiation. Note that it's guaranteed
70
// that nothing is imported into differentially-executed modules so
71
// this is expected to only take the module argument.
72
let instance = eval(&mut scope, "new WebAssembly.Instance(WASM_MODULE)")?;
73
74
Ok(Box::new(V8Instance {
75
isolate: self.isolate.clone(),
76
context: v8::Global::new(&mut scope, context),
77
instance: v8::Global::new(&mut scope, instance),
78
}))
79
}
80
81
fn assert_error_match(&self, err: &Error, wasmtime: &Trap) {
82
let v8 = err.to_string();
83
let wasmtime_msg = wasmtime.to_string();
84
let verify_wasmtime = |msg: &str| {
85
assert!(wasmtime_msg.contains(msg), "{wasmtime_msg}\n!=\n{v8}");
86
};
87
let verify_v8 = |msg: &[&str]| {
88
assert!(
89
msg.iter().any(|msg| v8.contains(msg)),
90
"{wasmtime_msg:?}\n\t!=\n{v8}"
91
);
92
};
93
match wasmtime {
94
Trap::MemoryOutOfBounds => {
95
return verify_v8(&["memory access out of bounds", "is out of bounds"]);
96
}
97
Trap::UnreachableCodeReached => {
98
return verify_v8(&[
99
"unreachable",
100
// All the wasms we test use wasm-smith's
101
// `ensure_termination` option which will `unreachable` when
102
// "fuel" runs out within the wasm module itself. This
103
// sometimes manifests as a call stack size exceeded in v8,
104
// however, since v8 sometimes has different limits on the
105
// call-stack especially when it's run multiple times. To
106
// get these error messages to line up allow v8 to say the
107
// call stack size exceeded when wasmtime says we hit
108
// unreachable.
109
"Maximum call stack size exceeded",
110
]);
111
}
112
Trap::IntegerDivisionByZero => {
113
return verify_v8(&["divide by zero", "remainder by zero"]);
114
}
115
Trap::StackOverflow => {
116
return verify_v8(&[
117
"call stack size exceeded",
118
// Similar to the above comment in `UnreachableCodeReached`
119
// if wasmtime hits a stack overflow but v8 ran all the way
120
// to when the `unreachable` instruction was hit then that's
121
// ok. This just means that wasmtime either has less optimal
122
// codegen or different limits on the stack than v8 does,
123
// which isn't an issue per-se.
124
"unreachable",
125
]);
126
}
127
Trap::IndirectCallToNull => return verify_v8(&["null function"]),
128
Trap::TableOutOfBounds => {
129
return verify_v8(&[
130
"table initializer is out of bounds",
131
"table index is out of bounds",
132
"element segment out of bounds",
133
]);
134
}
135
Trap::BadSignature => return verify_v8(&["function signature mismatch"]),
136
Trap::IntegerOverflow | Trap::BadConversionToInteger => {
137
return verify_v8(&[
138
"float unrepresentable in integer range",
139
"divide result unrepresentable",
140
]);
141
}
142
other => log::debug!("unknown code {other:?}"),
143
}
144
145
verify_wasmtime("not possibly present in an error, just panic please");
146
}
147
148
fn is_non_deterministic_error(&self, err: &Error) -> bool {
149
err.to_string().contains("Maximum call stack size exceeded")
150
}
151
}
152
153
struct V8Instance {
154
isolate: Rc<RefCell<v8::OwnedIsolate>>,
155
context: v8::Global<v8::Context>,
156
instance: v8::Global<v8::Value>,
157
}
158
159
impl DiffInstance for V8Instance {
160
fn name(&self) -> &'static str {
161
"v8"
162
}
163
164
fn evaluate(
165
&mut self,
166
function_name: &str,
167
arguments: &[DiffValue],
168
result_tys: &[DiffValueType],
169
) -> Result<Option<Vec<DiffValue>>> {
170
let mut isolate = self.isolate.borrow_mut();
171
let isolate = &mut **isolate;
172
let mut scope = v8::HandleScope::new(isolate);
173
let context = v8::Local::new(&mut scope, &self.context);
174
let global = context.global(&mut scope);
175
let mut scope = v8::ContextScope::new(&mut scope, context);
176
177
// See https://webassembly.github.io/spec/js-api/index.html#tojsvalue
178
// for how the Wasm-to-JS conversions are done.
179
let mut params = Vec::new();
180
for arg in arguments {
181
params.push(match *arg {
182
DiffValue::I32(n) => v8::Number::new(&mut scope, n.into()).into(),
183
DiffValue::F32(n) => v8::Number::new(&mut scope, f32::from_bits(n).into()).into(),
184
DiffValue::F64(n) => v8::Number::new(&mut scope, f64::from_bits(n)).into(),
185
DiffValue::I64(n) => v8::BigInt::new_from_i64(&mut scope, n).into(),
186
DiffValue::FuncRef { null } | DiffValue::ExternRef { null } => {
187
assert!(null);
188
v8::null(&mut scope).into()
189
}
190
// JS doesn't support v128 parameters
191
DiffValue::V128(_) => return Ok(None),
192
DiffValue::AnyRef { .. } => unimplemented!(),
193
DiffValue::ExnRef { .. } => unimplemented!(),
194
DiffValue::ContRef { .. } => unimplemented!(),
195
});
196
}
197
// JS doesn't support v128 return values
198
for ty in result_tys {
199
if let DiffValueType::V128 = ty {
200
return Ok(None);
201
}
202
}
203
204
let name = v8::String::new(&mut scope, "WASM_INSTANCE").unwrap();
205
let instance = v8::Local::new(&mut scope, &self.instance);
206
global.set(&mut scope, name.into(), instance);
207
let name = v8::String::new(&mut scope, "EXPORT_NAME").unwrap();
208
let func_name = v8::String::new(&mut scope, function_name).unwrap();
209
global.set(&mut scope, name.into(), func_name.into());
210
let name = v8::String::new(&mut scope, "ARGS").unwrap();
211
let params = v8::Array::new_with_elements(&mut scope, &params);
212
global.set(&mut scope, name.into(), params.into());
213
let v8_vals = eval(&mut scope, "WASM_INSTANCE.exports[EXPORT_NAME](...ARGS)")?;
214
215
let mut results = Vec::new();
216
match result_tys.len() {
217
0 => assert!(v8_vals.is_undefined()),
218
1 => results.push(get_diff_value(&v8_vals, result_tys[0], &mut scope)),
219
_ => {
220
let array = v8::Local::<'_, v8::Array>::try_from(v8_vals).unwrap();
221
for (i, ty) in result_tys.iter().enumerate() {
222
let v8 = array.get_index(&mut scope, i as u32).unwrap();
223
results.push(get_diff_value(&v8, *ty, &mut scope));
224
}
225
}
226
}
227
Ok(Some(results))
228
}
229
230
fn get_global(&mut self, global_name: &str, ty: DiffValueType) -> Option<DiffValue> {
231
if let DiffValueType::V128 = ty {
232
return None;
233
}
234
let mut isolate = self.isolate.borrow_mut();
235
let mut scope = v8::HandleScope::new(&mut *isolate);
236
let context = v8::Local::new(&mut scope, &self.context);
237
let global = context.global(&mut scope);
238
let mut scope = v8::ContextScope::new(&mut scope, context);
239
240
let name = v8::String::new(&mut scope, "GLOBAL_NAME").unwrap();
241
let memory_name = v8::String::new(&mut scope, global_name).unwrap();
242
global.set(&mut scope, name.into(), memory_name.into());
243
let val = eval(&mut scope, "WASM_INSTANCE.exports[GLOBAL_NAME].value").unwrap();
244
Some(get_diff_value(&val, ty, &mut scope))
245
}
246
247
fn get_memory(&mut self, memory_name: &str, shared: bool) -> Option<Vec<u8>> {
248
let mut isolate = self.isolate.borrow_mut();
249
let mut scope = v8::HandleScope::new(&mut *isolate);
250
let context = v8::Local::new(&mut scope, &self.context);
251
let global = context.global(&mut scope);
252
let mut scope = v8::ContextScope::new(&mut scope, context);
253
254
let name = v8::String::new(&mut scope, "MEMORY_NAME").unwrap();
255
let memory_name = v8::String::new(&mut scope, memory_name).unwrap();
256
global.set(&mut scope, name.into(), memory_name.into());
257
let v8 = eval(&mut scope, "WASM_INSTANCE.exports[MEMORY_NAME].buffer").unwrap();
258
let v8_data = if shared {
259
v8::Local::<'_, v8::SharedArrayBuffer>::try_from(v8)
260
.unwrap()
261
.get_backing_store()
262
} else {
263
v8::Local::<'_, v8::ArrayBuffer>::try_from(v8)
264
.unwrap()
265
.get_backing_store()
266
};
267
268
Some(v8_data.iter().map(|i| i.get()).collect())
269
}
270
}
271
272
/// Evaluates the JS `code` within `scope`, returning either the result of the
273
/// computation or the stringified exception if one happened.
274
fn eval<'s>(scope: &mut v8::HandleScope<'s>, code: &str) -> Result<v8::Local<'s, v8::Value>> {
275
let mut tc = v8::TryCatch::new(scope);
276
let mut scope = v8::EscapableHandleScope::new(&mut tc);
277
let source = v8::String::new(&mut scope, code).unwrap();
278
let script = v8::Script::compile(&mut scope, source, None).unwrap();
279
match script.run(&mut scope) {
280
Some(val) => Ok(scope.escape(val)),
281
None => {
282
drop(scope);
283
assert!(tc.has_caught());
284
bail!(
285
"{}",
286
tc.message()
287
.unwrap()
288
.get(&mut tc)
289
.to_rust_string_lossy(&mut tc)
290
)
291
}
292
}
293
}
294
295
fn get_diff_value(
296
val: &v8::Local<'_, v8::Value>,
297
ty: DiffValueType,
298
scope: &mut v8::HandleScope<'_>,
299
) -> DiffValue {
300
match ty {
301
DiffValueType::I32 => DiffValue::I32(val.to_int32(scope).unwrap().value()),
302
DiffValueType::I64 => {
303
let (val, todo) = val.to_big_int(scope).unwrap().i64_value();
304
assert!(todo);
305
DiffValue::I64(val)
306
}
307
DiffValueType::F32 => {
308
DiffValue::F32((val.to_number(scope).unwrap().value() as f32).to_bits())
309
}
310
DiffValueType::F64 => DiffValue::F64(val.to_number(scope).unwrap().value().to_bits()),
311
DiffValueType::FuncRef => DiffValue::FuncRef {
312
null: val.is_null(),
313
},
314
DiffValueType::ExternRef => DiffValue::ExternRef {
315
null: val.is_null(),
316
},
317
DiffValueType::AnyRef => unimplemented!(),
318
DiffValueType::ExnRef => unimplemented!(),
319
DiffValueType::V128 => unreachable!(),
320
DiffValueType::ContRef => unimplemented!(),
321
}
322
}
323
324
#[test]
325
fn smoke() {
326
crate::oracles::engine::smoke_test_engine(|_, config| Ok(V8Engine::new(config)))
327
}
328
329