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