1 use crate::generators::{Config, DiffValue, DiffValueType};
2 use crate::oracles::engine::{DiffEngine, DiffInstance};
3 use anyhow::{bail, Error, Result};
4 use std::cell::RefCell;
5 use std::rc::Rc;
6 use std::sync::Once;
7 use wasmtime::Trap;
8 use wasmtime::TrapCode;
9 
10 pub struct V8Engine {
11     isolate: Rc<RefCell<v8::OwnedIsolate>>,
12 }
13 
14 impl V8Engine {
15     pub fn new(config: &mut Config) -> V8Engine {
16         static INIT: Once = Once::new();
17 
18         INIT.call_once(|| {
19             let platform = v8::new_default_platform(0, false).make_shared();
20             v8::V8::initialize_platform(platform);
21             v8::V8::initialize();
22         });
23 
24         let config = &mut config.module_config.config;
25         // FIXME: reference types are disabled for now as we seemingly keep finding
26         // a segfault in v8. This is found relatively quickly locally and keeps
27         // getting found by oss-fuzz and currently we don't think that there's
28         // really much we can do about it. For the time being disable reference
29         // types entirely. An example bug is
30         // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=45662
31         config.reference_types_enabled = false;
32 
33         config.min_memories = config.min_memories.min(1);
34         config.max_memories = config.max_memories.min(1);
35         config.memory64_enabled = false;
36 
37         Self {
38             isolate: Rc::new(RefCell::new(v8::Isolate::new(Default::default()))),
39         }
40     }
41 }
42 
43 impl DiffEngine for V8Engine {
44     fn name(&self) -> &'static str {
45         "v8"
46     }
47 
48     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
49         // Setup a new `Context` in which we'll be creating this instance and
50         // executing code.
51         let mut isolate = self.isolate.borrow_mut();
52         let isolate = &mut **isolate;
53         let mut scope = v8::HandleScope::new(isolate);
54         let context = v8::Context::new(&mut scope);
55         let global = context.global(&mut scope);
56         let mut scope = v8::ContextScope::new(&mut scope, context);
57 
58         // Move the `wasm` into JS and then invoke `new WebAssembly.Module`.
59         let buf = v8::ArrayBuffer::new_backing_store_from_boxed_slice(wasm.into());
60         let buf = v8::SharedRef::from(buf);
61         let name = v8::String::new(&mut scope, "WASM_BINARY").unwrap();
62         let buf = v8::ArrayBuffer::with_backing_store(&mut scope, &buf);
63         global.set(&mut scope, name.into(), buf.into());
64         let module = eval(&mut scope, "new WebAssembly.Module(WASM_BINARY)").unwrap();
65         let name = v8::String::new(&mut scope, "WASM_MODULE").unwrap();
66         global.set(&mut scope, name.into(), module);
67 
68         // Using our `WASM_MODULE` run instantiation. Note that it's guaranteed
69         // that nothing is imported into differentially-executed modules so
70         // this is expected to only take the module argument.
71         let instance = eval(&mut scope, "new WebAssembly.Instance(WASM_MODULE)")?;
72 
73         Ok(Box::new(V8Instance {
74             isolate: self.isolate.clone(),
75             context: v8::Global::new(&mut scope, context),
76             instance: v8::Global::new(&mut scope, instance),
77         }))
78     }
79 
80     fn assert_error_match(&self, wasmtime: &Trap, err: &Error) {
81         let v8 = err.to_string();
82         let wasmtime_msg = wasmtime.to_string();
83         let verify_wasmtime = |msg: &str| {
84             assert!(wasmtime_msg.contains(msg), "{}\n!=\n{}", wasmtime_msg, v8);
85         };
86         let verify_v8 = |msg: &[&str]| {
87             assert!(
88                 msg.iter().any(|msg| v8.contains(msg)),
89                 "{:?}\n\t!=\n{}",
90                 wasmtime_msg,
91                 v8
92             );
93         };
94         match wasmtime.trap_code() {
95             Some(TrapCode::MemoryOutOfBounds) => {
96                 return verify_v8(&[
97                     "memory access out of bounds",
98                     "data segment is out of bounds",
99                 ])
100             }
101             Some(TrapCode::UnreachableCodeReached) => {
102                 return verify_v8(&[
103                     "unreachable",
104                     // All the wasms we test use wasm-smith's
105                     // `ensure_termination` option which will `unreachable` when
106                     // "fuel" runs out within the wasm module itself. This
107                     // sometimes manifests as a call stack size exceeded in v8,
108                     // however, since v8 sometimes has different limits on the
109                     // call-stack especially when it's run multiple times. To
110                     // get these error messages to line up allow v8 to say the
111                     // call stack size exceeded when wasmtime says we hit
112                     // unreachable.
113                     "Maximum call stack size exceeded",
114                 ]);
115             }
116             Some(TrapCode::IntegerDivisionByZero) => {
117                 return verify_v8(&["divide by zero", "remainder by zero"])
118             }
119             Some(TrapCode::StackOverflow) => {
120                 return verify_v8(&[
121                     "call stack size exceeded",
122                     // Similar to the above comment in `UnreachableCodeReached`
123                     // if wasmtime hits a stack overflow but v8 ran all the way
124                     // to when the `unreachable` instruction was hit then that's
125                     // ok. This just means that wasmtime either has less optimal
126                     // codegen or different limits on the stack than v8 does,
127                     // which isn't an issue per-se.
128                     "unreachable",
129                 ]);
130             }
131             Some(TrapCode::IndirectCallToNull) => return verify_v8(&["null function"]),
132             Some(TrapCode::TableOutOfBounds) => {
133                 return verify_v8(&[
134                     "table initializer is out of bounds",
135                     "table index is out of bounds",
136                 ])
137             }
138             Some(TrapCode::BadSignature) => return verify_v8(&["function signature mismatch"]),
139             Some(TrapCode::IntegerOverflow) | Some(TrapCode::BadConversionToInteger) => {
140                 return verify_v8(&[
141                     "float unrepresentable in integer range",
142                     "divide result unrepresentable",
143                 ])
144             }
145             other => log::debug!("unknown code {:?}", other),
146         }
147 
148         verify_wasmtime("not possibly present in an error, just panic please");
149     }
150 
151     fn is_stack_overflow(&self, err: &Error) -> bool {
152         err.to_string().contains("Maximum call stack size exceeded")
153     }
154 }
155 
156 struct V8Instance {
157     isolate: Rc<RefCell<v8::OwnedIsolate>>,
158     context: v8::Global<v8::Context>,
159     instance: v8::Global<v8::Value>,
160 }
161 
162 impl DiffInstance for V8Instance {
163     fn name(&self) -> &'static str {
164         "v8"
165     }
166 
167     fn evaluate(
168         &mut self,
169         function_name: &str,
170         arguments: &[DiffValue],
171         result_tys: &[DiffValueType],
172     ) -> Result<Option<Vec<DiffValue>>> {
173         let mut isolate = self.isolate.borrow_mut();
174         let isolate = &mut **isolate;
175         let mut scope = v8::HandleScope::new(isolate);
176         let context = v8::Local::new(&mut scope, &self.context);
177         let global = context.global(&mut scope);
178         let mut scope = v8::ContextScope::new(&mut scope, context);
179 
180         // See https://webassembly.github.io/spec/js-api/index.html#tojsvalue
181         // for how the Wasm-to-JS conversions are done.
182         let mut params = Vec::new();
183         for arg in arguments {
184             params.push(match *arg {
185                 DiffValue::I32(n) => v8::Number::new(&mut scope, n.into()).into(),
186                 DiffValue::F32(n) => v8::Number::new(&mut scope, f32::from_bits(n).into()).into(),
187                 DiffValue::F64(n) => v8::Number::new(&mut scope, f64::from_bits(n)).into(),
188                 DiffValue::I64(n) => v8::BigInt::new_from_i64(&mut scope, n).into(),
189                 DiffValue::FuncRef { null } | DiffValue::ExternRef { null } => {
190                     assert!(null);
191                     v8::null(&mut scope).into()
192                 }
193                 // JS doesn't support v128 parameters
194                 DiffValue::V128(_) => return Ok(None),
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() as i32),
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::V128 => unreachable!(),
318     }
319 }
320 
321 #[test]
322 fn smoke() {
323     crate::oracles::engine::smoke_test_engine(|_, config| Ok(V8Engine::new(config)))
324 }
325