Skip to main content

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