Path: blob/main/crates/fuzzing/src/oracles/diff_v8.rs
1693 views
use crate::generators::{Config, DiffValue, DiffValueType};1use crate::oracles::engine::{DiffEngine, DiffInstance};2use anyhow::{Error, Result, bail};3use std::cell::RefCell;4use std::rc::Rc;5use std::sync::Once;6use wasmtime::Trap;78pub struct V8Engine {9isolate: Rc<RefCell<v8::OwnedIsolate>>,10}1112impl V8Engine {13pub fn new(config: &mut Config) -> V8Engine {14static INIT: Once = Once::new();1516INIT.call_once(|| {17let platform = v8::new_default_platform(0, false).make_shared();18v8::V8::initialize_platform(platform);19v8::V8::initialize();20});2122let config = &mut config.module_config.config;23// FIXME: reference types are disabled for now as we seemingly keep finding24// a segfault in v8. This is found relatively quickly locally and keeps25// getting found by oss-fuzz and currently we don't think that there's26// really much we can do about it. For the time being disable reference27// types entirely. An example bug is28// https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=4566229config.reference_types_enabled = false;3031config.min_memories = config.min_memories.min(1);32config.max_memories = config.max_memories.min(1);33config.memory64_enabled = false;34config.custom_page_sizes_enabled = false;35config.wide_arithmetic_enabled = false;3637Self {38isolate: Rc::new(RefCell::new(v8::Isolate::new(Default::default()))),39}40}41}4243impl DiffEngine for V8Engine {44fn name(&self) -> &'static str {45"v8"46}4748fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {49// Setup a new `Context` in which we'll be creating this instance and50// executing code.51let mut isolate = self.isolate.borrow_mut();52let isolate = &mut **isolate;53let mut scope = v8::HandleScope::new(isolate);54let context = v8::Context::new(&mut scope, Default::default());55let global = context.global(&mut scope);56let mut scope = v8::ContextScope::new(&mut scope, context);5758// Move the `wasm` into JS and then invoke `new WebAssembly.Module`.59let buf = v8::ArrayBuffer::new_backing_store_from_boxed_slice(wasm.into());60let buf = v8::SharedRef::from(buf);61let name = v8::String::new(&mut scope, "WASM_BINARY").unwrap();62let buf = v8::ArrayBuffer::with_backing_store(&mut scope, &buf);63global.set(&mut scope, name.into(), buf.into());64let module = eval(&mut scope, "new WebAssembly.Module(WASM_BINARY)").unwrap();65let name = v8::String::new(&mut scope, "WASM_MODULE").unwrap();66global.set(&mut scope, name.into(), module);6768// Using our `WASM_MODULE` run instantiation. Note that it's guaranteed69// that nothing is imported into differentially-executed modules so70// this is expected to only take the module argument.71let instance = eval(&mut scope, "new WebAssembly.Instance(WASM_MODULE)")?;7273Ok(Box::new(V8Instance {74isolate: self.isolate.clone(),75context: v8::Global::new(&mut scope, context),76instance: v8::Global::new(&mut scope, instance),77}))78}7980fn assert_error_match(&self, err: &Error, wasmtime: &Trap) {81let v8 = err.to_string();82let wasmtime_msg = wasmtime.to_string();83let verify_wasmtime = |msg: &str| {84assert!(wasmtime_msg.contains(msg), "{wasmtime_msg}\n!=\n{v8}");85};86let verify_v8 = |msg: &[&str]| {87assert!(88msg.iter().any(|msg| v8.contains(msg)),89"{wasmtime_msg:?}\n\t!=\n{v8}"90);91};92match wasmtime {93Trap::MemoryOutOfBounds => {94return verify_v8(&["memory access out of bounds", "is out of bounds"]);95}96Trap::UnreachableCodeReached => {97return verify_v8(&[98"unreachable",99// All the wasms we test use wasm-smith's100// `ensure_termination` option which will `unreachable` when101// "fuel" runs out within the wasm module itself. This102// sometimes manifests as a call stack size exceeded in v8,103// however, since v8 sometimes has different limits on the104// call-stack especially when it's run multiple times. To105// get these error messages to line up allow v8 to say the106// call stack size exceeded when wasmtime says we hit107// unreachable.108"Maximum call stack size exceeded",109]);110}111Trap::IntegerDivisionByZero => {112return verify_v8(&["divide by zero", "remainder by zero"]);113}114Trap::StackOverflow => {115return 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 way119// to when the `unreachable` instruction was hit then that's120// ok. This just means that wasmtime either has less optimal121// codegen or different limits on the stack than v8 does,122// which isn't an issue per-se.123"unreachable",124]);125}126Trap::IndirectCallToNull => return verify_v8(&["null function"]),127Trap::TableOutOfBounds => {128return verify_v8(&[129"table initializer is out of bounds",130"table index is out of bounds",131"element segment out of bounds",132]);133}134Trap::BadSignature => return verify_v8(&["function signature mismatch"]),135Trap::IntegerOverflow | Trap::BadConversionToInteger => {136return verify_v8(&[137"float unrepresentable in integer range",138"divide result unrepresentable",139]);140}141other => log::debug!("unknown code {other:?}"),142}143144verify_wasmtime("not possibly present in an error, just panic please");145}146147fn is_non_deterministic_error(&self, err: &Error) -> bool {148err.to_string().contains("Maximum call stack size exceeded")149}150}151152struct V8Instance {153isolate: Rc<RefCell<v8::OwnedIsolate>>,154context: v8::Global<v8::Context>,155instance: v8::Global<v8::Value>,156}157158impl DiffInstance for V8Instance {159fn name(&self) -> &'static str {160"v8"161}162163fn evaluate(164&mut self,165function_name: &str,166arguments: &[DiffValue],167result_tys: &[DiffValueType],168) -> Result<Option<Vec<DiffValue>>> {169let mut isolate = self.isolate.borrow_mut();170let isolate = &mut **isolate;171let mut scope = v8::HandleScope::new(isolate);172let context = v8::Local::new(&mut scope, &self.context);173let global = context.global(&mut scope);174let mut scope = v8::ContextScope::new(&mut scope, context);175176// See https://webassembly.github.io/spec/js-api/index.html#tojsvalue177// for how the Wasm-to-JS conversions are done.178let mut params = Vec::new();179for arg in arguments {180params.push(match *arg {181DiffValue::I32(n) => v8::Number::new(&mut scope, n.into()).into(),182DiffValue::F32(n) => v8::Number::new(&mut scope, f32::from_bits(n).into()).into(),183DiffValue::F64(n) => v8::Number::new(&mut scope, f64::from_bits(n)).into(),184DiffValue::I64(n) => v8::BigInt::new_from_i64(&mut scope, n).into(),185DiffValue::FuncRef { null } | DiffValue::ExternRef { null } => {186assert!(null);187v8::null(&mut scope).into()188}189// JS doesn't support v128 parameters190DiffValue::V128(_) => return Ok(None),191DiffValue::AnyRef { .. } => unimplemented!(),192DiffValue::ExnRef { .. } => unimplemented!(),193DiffValue::ContRef { .. } => unimplemented!(),194});195}196// JS doesn't support v128 return values197for ty in result_tys {198if let DiffValueType::V128 = ty {199return Ok(None);200}201}202203let name = v8::String::new(&mut scope, "WASM_INSTANCE").unwrap();204let instance = v8::Local::new(&mut scope, &self.instance);205global.set(&mut scope, name.into(), instance);206let name = v8::String::new(&mut scope, "EXPORT_NAME").unwrap();207let func_name = v8::String::new(&mut scope, function_name).unwrap();208global.set(&mut scope, name.into(), func_name.into());209let name = v8::String::new(&mut scope, "ARGS").unwrap();210let params = v8::Array::new_with_elements(&mut scope, ¶ms);211global.set(&mut scope, name.into(), params.into());212let v8_vals = eval(&mut scope, "WASM_INSTANCE.exports[EXPORT_NAME](...ARGS)")?;213214let mut results = Vec::new();215match result_tys.len() {2160 => assert!(v8_vals.is_undefined()),2171 => results.push(get_diff_value(&v8_vals, result_tys[0], &mut scope)),218_ => {219let array = v8::Local::<'_, v8::Array>::try_from(v8_vals).unwrap();220for (i, ty) in result_tys.iter().enumerate() {221let v8 = array.get_index(&mut scope, i as u32).unwrap();222results.push(get_diff_value(&v8, *ty, &mut scope));223}224}225}226Ok(Some(results))227}228229fn get_global(&mut self, global_name: &str, ty: DiffValueType) -> Option<DiffValue> {230if let DiffValueType::V128 = ty {231return None;232}233let mut isolate = self.isolate.borrow_mut();234let mut scope = v8::HandleScope::new(&mut *isolate);235let context = v8::Local::new(&mut scope, &self.context);236let global = context.global(&mut scope);237let mut scope = v8::ContextScope::new(&mut scope, context);238239let name = v8::String::new(&mut scope, "GLOBAL_NAME").unwrap();240let memory_name = v8::String::new(&mut scope, global_name).unwrap();241global.set(&mut scope, name.into(), memory_name.into());242let val = eval(&mut scope, "WASM_INSTANCE.exports[GLOBAL_NAME].value").unwrap();243Some(get_diff_value(&val, ty, &mut scope))244}245246fn get_memory(&mut self, memory_name: &str, shared: bool) -> Option<Vec<u8>> {247let mut isolate = self.isolate.borrow_mut();248let mut scope = v8::HandleScope::new(&mut *isolate);249let context = v8::Local::new(&mut scope, &self.context);250let global = context.global(&mut scope);251let mut scope = v8::ContextScope::new(&mut scope, context);252253let name = v8::String::new(&mut scope, "MEMORY_NAME").unwrap();254let memory_name = v8::String::new(&mut scope, memory_name).unwrap();255global.set(&mut scope, name.into(), memory_name.into());256let v8 = eval(&mut scope, "WASM_INSTANCE.exports[MEMORY_NAME].buffer").unwrap();257let v8_data = if shared {258v8::Local::<'_, v8::SharedArrayBuffer>::try_from(v8)259.unwrap()260.get_backing_store()261} else {262v8::Local::<'_, v8::ArrayBuffer>::try_from(v8)263.unwrap()264.get_backing_store()265};266267Some(v8_data.iter().map(|i| i.get()).collect())268}269}270271/// Evaluates the JS `code` within `scope`, returning either the result of the272/// computation or the stringified exception if one happened.273fn eval<'s>(scope: &mut v8::HandleScope<'s>, code: &str) -> Result<v8::Local<'s, v8::Value>> {274let mut tc = v8::TryCatch::new(scope);275let mut scope = v8::EscapableHandleScope::new(&mut tc);276let source = v8::String::new(&mut scope, code).unwrap();277let script = v8::Script::compile(&mut scope, source, None).unwrap();278match script.run(&mut scope) {279Some(val) => Ok(scope.escape(val)),280None => {281drop(scope);282assert!(tc.has_caught());283bail!(284"{}",285tc.message()286.unwrap()287.get(&mut tc)288.to_rust_string_lossy(&mut tc)289)290}291}292}293294fn get_diff_value(295val: &v8::Local<'_, v8::Value>,296ty: DiffValueType,297scope: &mut v8::HandleScope<'_>,298) -> DiffValue {299match ty {300DiffValueType::I32 => DiffValue::I32(val.to_int32(scope).unwrap().value()),301DiffValueType::I64 => {302let (val, todo) = val.to_big_int(scope).unwrap().i64_value();303assert!(todo);304DiffValue::I64(val)305}306DiffValueType::F32 => {307DiffValue::F32((val.to_number(scope).unwrap().value() as f32).to_bits())308}309DiffValueType::F64 => DiffValue::F64(val.to_number(scope).unwrap().value().to_bits()),310DiffValueType::FuncRef => DiffValue::FuncRef {311null: val.is_null(),312},313DiffValueType::ExternRef => DiffValue::ExternRef {314null: val.is_null(),315},316DiffValueType::AnyRef => unimplemented!(),317DiffValueType::ExnRef => unimplemented!(),318DiffValueType::V128 => unreachable!(),319DiffValueType::ContRef => unimplemented!(),320}321}322323#[test]324fn smoke() {325crate::oracles::engine::smoke_test_engine(|_, config| Ok(V8Engine::new(config)))326}327328329