wasmtime_fuzzing/oracles/
diff_wasmi.rs

1//! Evaluate an exported Wasm function using the wasmi interpreter.
2
3use crate::generators::{Config, DiffValue, DiffValueType};
4use crate::oracles::engine::{DiffEngine, DiffInstance};
5use anyhow::{Context, Error, Result};
6use wasmtime::Trap;
7
8/// A wrapper for `wasmi` as a [`DiffEngine`].
9pub struct WasmiEngine {
10    engine: wasmi::Engine,
11}
12
13impl WasmiEngine {
14    pub(crate) fn new(config: &mut Config) -> Self {
15        let config = &mut config.module_config.config;
16        // Force generated Wasm modules to never have features that Wasmi doesn't support.
17        config.relaxed_simd_enabled = false;
18        config.threads_enabled = false;
19        config.exceptions_enabled = false;
20        config.gc_enabled = false;
21
22        // FIXME: once the active fuzz bug for wasmi's simd differential fuzzing
23        // has been fixed and we've updated then this should be re-enabled.
24        config.simd_enabled = false;
25        // FIXME: requires updating to a wasmi that contains
26        // wasmi-labs/wasmi#1531.
27        config.memory64_enabled = false;
28        // FIXME: until https://github.com/wasmi-labs/wasmi/issues/1544 is fixed.
29        config.wide_arithmetic_enabled = false;
30
31        let mut wasmi_config = wasmi::Config::default();
32        wasmi_config
33            .consume_fuel(false)
34            .floats(true)
35            .wasm_mutable_global(true)
36            .wasm_sign_extension(config.sign_extension_ops_enabled)
37            .wasm_saturating_float_to_int(config.saturating_float_to_int_enabled)
38            .wasm_multi_value(config.multi_value_enabled)
39            .wasm_bulk_memory(config.bulk_memory_enabled)
40            .wasm_reference_types(config.reference_types_enabled)
41            .wasm_tail_call(config.tail_call_enabled)
42            .wasm_multi_memory(config.max_memories > 1)
43            .wasm_extended_const(config.extended_const_enabled)
44            .wasm_custom_page_sizes(config.custom_page_sizes_enabled)
45            .wasm_memory64(config.memory64_enabled)
46            .wasm_simd(config.simd_enabled)
47            .wasm_wide_arithmetic(config.wide_arithmetic_enabled);
48        Self {
49            engine: wasmi::Engine::new(&wasmi_config),
50        }
51    }
52
53    fn trap_code(&self, err: &Error) -> Option<wasmi::core::TrapCode> {
54        let err = err.downcast_ref::<wasmi::Error>()?;
55        if let Some(code) = err.as_trap_code() {
56            return Some(code);
57        }
58
59        match err.kind() {
60            wasmi::errors::ErrorKind::Instantiation(
61                wasmi::errors::InstantiationError::ElementSegmentDoesNotFit { .. },
62            ) => Some(wasmi::core::TrapCode::TableOutOfBounds),
63            wasmi::errors::ErrorKind::Memory(wasmi::errors::MemoryError::OutOfBoundsAccess) => {
64                Some(wasmi::core::TrapCode::MemoryOutOfBounds)
65            }
66            _ => {
67                log::trace!("unknown wasmi error: {:?}", err.kind());
68                None
69            }
70        }
71    }
72}
73
74impl DiffEngine for WasmiEngine {
75    fn name(&self) -> &'static str {
76        "wasmi"
77    }
78
79    fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
80        let module =
81            wasmi::Module::new(&self.engine, wasm).context("unable to validate Wasm module")?;
82        let mut store = wasmi::Store::new(&self.engine, ());
83        let instance = wasmi::Linker::<()>::new(&self.engine)
84            .instantiate(&mut store, &module)
85            .and_then(|i| i.start(&mut store))
86            .context("unable to instantiate module in wasmi")?;
87        Ok(Box::new(WasmiInstance { store, instance }))
88    }
89
90    fn assert_error_match(&self, lhs: &Error, rhs: &Trap) {
91        match self.trap_code(lhs) {
92            Some(code) => assert_eq!(wasmi_to_wasmtime_trap_code(code), *rhs),
93            None => panic!("unexpected wasmi error {lhs:?}"),
94        }
95    }
96
97    fn is_non_deterministic_error(&self, err: &Error) -> bool {
98        matches!(
99            self.trap_code(err),
100            Some(wasmi::core::TrapCode::StackOverflow)
101        )
102    }
103}
104
105/// Converts `wasmi` trap code to `wasmtime` trap code.
106fn wasmi_to_wasmtime_trap_code(trap: wasmi::core::TrapCode) -> Trap {
107    use wasmi::core::TrapCode;
108    match trap {
109        TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
110        TrapCode::MemoryOutOfBounds => Trap::MemoryOutOfBounds,
111        TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
112        TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
113        TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
114        TrapCode::IntegerOverflow => Trap::IntegerOverflow,
115        TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
116        TrapCode::StackOverflow => Trap::StackOverflow,
117        TrapCode::BadSignature => Trap::BadSignature,
118        TrapCode::OutOfFuel => unimplemented!("built-in fuel metering is unused"),
119        TrapCode::GrowthOperationLimited => unimplemented!("resource limiter is unused"),
120    }
121}
122
123/// A wrapper for `wasmi` Wasm instances.
124struct WasmiInstance {
125    store: wasmi::Store<()>,
126    instance: wasmi::Instance,
127}
128
129impl DiffInstance for WasmiInstance {
130    fn name(&self) -> &'static str {
131        "wasmi"
132    }
133
134    fn evaluate(
135        &mut self,
136        function_name: &str,
137        arguments: &[DiffValue],
138        result_tys: &[DiffValueType],
139    ) -> Result<Option<Vec<DiffValue>>> {
140        let function = self
141            .instance
142            .get_export(&self.store, function_name)
143            .and_then(wasmi::Extern::into_func)
144            .unwrap();
145        let arguments: Vec<_> = arguments.iter().map(|x| x.into()).collect();
146        let mut results = vec![wasmi::Val::I32(0); result_tys.len()];
147        function
148            .call(&mut self.store, &arguments, &mut results)
149            .context("wasmi function trap")?;
150        Ok(Some(results.into_iter().map(Into::into).collect()))
151    }
152
153    fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
154        Some(
155            self.instance
156                .get_export(&self.store, name)
157                .unwrap()
158                .into_global()
159                .unwrap()
160                .get(&self.store)
161                .into(),
162        )
163    }
164
165    fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
166        assert!(!shared);
167        Some(
168            self.instance
169                .get_export(&self.store, name)
170                .unwrap()
171                .into_memory()
172                .unwrap()
173                .data(&self.store)
174                .to_vec(),
175        )
176    }
177}
178
179impl From<&DiffValue> for wasmi::Val {
180    fn from(v: &DiffValue) -> Self {
181        use wasmi::Val as WasmiValue;
182        match *v {
183            DiffValue::I32(n) => WasmiValue::I32(n),
184            DiffValue::I64(n) => WasmiValue::I64(n),
185            DiffValue::F32(n) => WasmiValue::F32(wasmi::core::F32::from_bits(n)),
186            DiffValue::F64(n) => WasmiValue::F64(wasmi::core::F64::from_bits(n)),
187            DiffValue::V128(n) => WasmiValue::V128(wasmi::core::V128::from(n)),
188            DiffValue::FuncRef { null } => {
189                assert!(null);
190                WasmiValue::FuncRef(wasmi::FuncRef::null())
191            }
192            DiffValue::ExternRef { null } => {
193                assert!(null);
194                WasmiValue::ExternRef(wasmi::ExternRef::null())
195            }
196            DiffValue::AnyRef { .. } => unimplemented!(),
197            DiffValue::ExnRef { .. } => unimplemented!(),
198            DiffValue::ContRef { .. } => unimplemented!(),
199        }
200    }
201}
202
203impl From<wasmi::Val> for DiffValue {
204    fn from(value: wasmi::Val) -> Self {
205        use wasmi::Val as WasmiValue;
206        match value {
207            WasmiValue::I32(n) => DiffValue::I32(n),
208            WasmiValue::I64(n) => DiffValue::I64(n),
209            WasmiValue::F32(n) => DiffValue::F32(n.to_bits()),
210            WasmiValue::F64(n) => DiffValue::F64(n.to_bits()),
211            WasmiValue::V128(n) => DiffValue::V128(n.as_u128()),
212            WasmiValue::FuncRef(f) => DiffValue::FuncRef { null: f.is_null() },
213            WasmiValue::ExternRef(e) => DiffValue::ExternRef { null: e.is_null() },
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn smoke() {
224        crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config)))
225    }
226}