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, ¶ms); 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