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