wasmtime_fuzzing/oracles/
diff_wasmi.rs1use crate::generators::{Config, DiffValue, DiffValueType};
4use crate::oracles::engine::{DiffEngine, DiffInstance};
5use anyhow::{Context, Error, Result};
6use wasmtime::Trap;
7
8pub 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 config.relaxed_simd_enabled = false;
18 config.threads_enabled = false;
19 config.exceptions_enabled = false;
20 config.gc_enabled = false;
21
22 config.simd_enabled = false;
25 config.memory64_enabled = false;
28 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
105fn 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
123struct 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}