wasmtime_fuzzing/oracles/
diff_v8.rs1use crate::generators::{Config, DiffValue, DiffValueType};
2use crate::oracles::engine::{DiffEngine, DiffInstance};
3use anyhow::{bail, Error, Result};
4use std::cell::RefCell;
5use std::rc::Rc;
6use std::sync::Once;
7use wasmtime::Trap;
8
9pub struct V8Engine {
10 isolate: Rc<RefCell<v8::OwnedIsolate>>,
11}
12
13impl 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 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
44impl 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 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 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 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 "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 "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
153struct V8Instance {
154 isolate: Rc<RefCell<v8::OwnedIsolate>>,
155 context: v8::Global<v8::Context>,
156 instance: v8::Global<v8::Value>,
157}
158
159impl 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 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 DiffValue::V128(_) => return Ok(None),
192 DiffValue::AnyRef { .. } => unimplemented!(),
193 });
194 }
195 for ty in result_tys {
197 if let DiffValueType::V128 = ty {
198 return Ok(None);
199 }
200 }
201
202 let name = v8::String::new(&mut scope, "WASM_INSTANCE").unwrap();
203 let instance = v8::Local::new(&mut scope, &self.instance);
204 global.set(&mut scope, name.into(), instance);
205 let name = v8::String::new(&mut scope, "EXPORT_NAME").unwrap();
206 let func_name = v8::String::new(&mut scope, function_name).unwrap();
207 global.set(&mut scope, name.into(), func_name.into());
208 let name = v8::String::new(&mut scope, "ARGS").unwrap();
209 let params = v8::Array::new_with_elements(&mut scope, ¶ms);
210 global.set(&mut scope, name.into(), params.into());
211 let v8_vals = eval(&mut scope, "WASM_INSTANCE.exports[EXPORT_NAME](...ARGS)")?;
212
213 let mut results = Vec::new();
214 match result_tys.len() {
215 0 => assert!(v8_vals.is_undefined()),
216 1 => results.push(get_diff_value(&v8_vals, result_tys[0], &mut scope)),
217 _ => {
218 let array = v8::Local::<'_, v8::Array>::try_from(v8_vals).unwrap();
219 for (i, ty) in result_tys.iter().enumerate() {
220 let v8 = array.get_index(&mut scope, i as u32).unwrap();
221 results.push(get_diff_value(&v8, *ty, &mut scope));
222 }
223 }
224 }
225 Ok(Some(results))
226 }
227
228 fn get_global(&mut self, global_name: &str, ty: DiffValueType) -> Option<DiffValue> {
229 if let DiffValueType::V128 = ty {
230 return None;
231 }
232 let mut isolate = self.isolate.borrow_mut();
233 let mut scope = v8::HandleScope::new(&mut *isolate);
234 let context = v8::Local::new(&mut scope, &self.context);
235 let global = context.global(&mut scope);
236 let mut scope = v8::ContextScope::new(&mut scope, context);
237
238 let name = v8::String::new(&mut scope, "GLOBAL_NAME").unwrap();
239 let memory_name = v8::String::new(&mut scope, global_name).unwrap();
240 global.set(&mut scope, name.into(), memory_name.into());
241 let val = eval(&mut scope, "WASM_INSTANCE.exports[GLOBAL_NAME].value").unwrap();
242 Some(get_diff_value(&val, ty, &mut scope))
243 }
244
245 fn get_memory(&mut self, memory_name: &str, shared: bool) -> Option<Vec<u8>> {
246 let mut isolate = self.isolate.borrow_mut();
247 let mut scope = v8::HandleScope::new(&mut *isolate);
248 let context = v8::Local::new(&mut scope, &self.context);
249 let global = context.global(&mut scope);
250 let mut scope = v8::ContextScope::new(&mut scope, context);
251
252 let name = v8::String::new(&mut scope, "MEMORY_NAME").unwrap();
253 let memory_name = v8::String::new(&mut scope, memory_name).unwrap();
254 global.set(&mut scope, name.into(), memory_name.into());
255 let v8 = eval(&mut scope, "WASM_INSTANCE.exports[MEMORY_NAME].buffer").unwrap();
256 let v8_data = if shared {
257 v8::Local::<'_, v8::SharedArrayBuffer>::try_from(v8)
258 .unwrap()
259 .get_backing_store()
260 } else {
261 v8::Local::<'_, v8::ArrayBuffer>::try_from(v8)
262 .unwrap()
263 .get_backing_store()
264 };
265
266 Some(v8_data.iter().map(|i| i.get()).collect())
267 }
268}
269
270fn eval<'s>(scope: &mut v8::HandleScope<'s>, code: &str) -> Result<v8::Local<'s, v8::Value>> {
273 let mut tc = v8::TryCatch::new(scope);
274 let mut scope = v8::EscapableHandleScope::new(&mut tc);
275 let source = v8::String::new(&mut scope, code).unwrap();
276 let script = v8::Script::compile(&mut scope, source, None).unwrap();
277 match script.run(&mut scope) {
278 Some(val) => Ok(scope.escape(val)),
279 None => {
280 drop(scope);
281 assert!(tc.has_caught());
282 bail!(
283 "{}",
284 tc.message()
285 .unwrap()
286 .get(&mut tc)
287 .to_rust_string_lossy(&mut tc)
288 )
289 }
290 }
291}
292
293fn get_diff_value(
294 val: &v8::Local<'_, v8::Value>,
295 ty: DiffValueType,
296 scope: &mut v8::HandleScope<'_>,
297) -> DiffValue {
298 match ty {
299 DiffValueType::I32 => DiffValue::I32(val.to_int32(scope).unwrap().value()),
300 DiffValueType::I64 => {
301 let (val, todo) = val.to_big_int(scope).unwrap().i64_value();
302 assert!(todo);
303 DiffValue::I64(val)
304 }
305 DiffValueType::F32 => {
306 DiffValue::F32((val.to_number(scope).unwrap().value() as f32).to_bits())
307 }
308 DiffValueType::F64 => DiffValue::F64(val.to_number(scope).unwrap().value().to_bits()),
309 DiffValueType::FuncRef => DiffValue::FuncRef {
310 null: val.is_null(),
311 },
312 DiffValueType::ExternRef => DiffValue::ExternRef {
313 null: val.is_null(),
314 },
315 DiffValueType::AnyRef => unimplemented!(),
316 DiffValueType::V128 => unreachable!(),
317 }
318}
319
320#[test]
321fn smoke() {
322 crate::oracles::engine::smoke_test_engine(|_, config| Ok(V8Engine::new(config)))
323}