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