1 //! Evaluate an exported Wasm function using Wasmtime.
2 
3 use crate::generators::{self, CompilerStrategy, DiffValue, DiffValueType, WasmtimeConfig};
4 use crate::oracles::dummy;
5 use crate::oracles::engine::DiffInstance;
6 use crate::oracles::{compile_module, engine::DiffEngine, StoreLimits};
7 use crate::single_module_fuzzer::KnownValid;
8 use anyhow::{Context, Error, Result};
9 use arbitrary::Unstructured;
10 use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, Val};
11 
12 /// A wrapper for using Wasmtime as a [`DiffEngine`].
13 pub struct WasmtimeEngine {
14     config: generators::Config,
15 }
16 
17 impl WasmtimeEngine {
18     /// Merely store the configuration; the engine is actually constructed
19     /// later. Ideally the store and engine could be built here but
20     /// `compile_module` takes a [`generators::Config`]; TODO re-factor this if
21     /// that ever changes.
22     pub fn new(
23         u: &mut Unstructured<'_>,
24         config: &mut generators::Config,
25         compiler_strategy: CompilerStrategy,
26     ) -> arbitrary::Result<Self> {
27         if let CompilerStrategy::Winch = compiler_strategy {
28             config.disable_unimplemented_winch_proposals();
29         }
30         let mut new_config = u.arbitrary::<WasmtimeConfig>()?;
31         new_config.compiler_strategy = compiler_strategy;
32         new_config.make_compatible_with(&config.wasmtime);
33 
34         let config = generators::Config {
35             wasmtime: new_config,
36             module_config: config.module_config.clone(),
37         };
38         Ok(Self { config })
39     }
40 }
41 
42 impl DiffEngine for WasmtimeEngine {
43     fn name(&self) -> &'static str {
44         match self.config.wasmtime.compiler_strategy {
45             CompilerStrategy::Cranelift => "wasmtime",
46             CompilerStrategy::Winch => "winch",
47         }
48     }
49 
50     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
51         let store = self.config.to_store();
52         let module = compile_module(store.engine(), wasm, KnownValid::Yes, &self.config).unwrap();
53         let instance = WasmtimeInstance::new(store, module)?;
54         Ok(Box::new(instance))
55     }
56 
57     fn assert_error_match(&self, trap: &Trap, err: &Error) {
58         let trap2 = err
59             .downcast_ref::<Trap>()
60             .expect(&format!("not a trap: {err:?}"));
61         assert_eq!(trap, trap2, "{trap}\nis not equal to\n{trap2}");
62     }
63 
64     fn is_stack_overflow(&self, err: &Error) -> bool {
65         match err.downcast_ref::<Trap>() {
66             Some(trap) => *trap == Trap::StackOverflow,
67             None => false,
68         }
69     }
70 }
71 
72 /// A wrapper around a Wasmtime instance.
73 ///
74 /// The Wasmtime engine constructs a new store and compiles an instance of a
75 /// Wasm module.
76 pub struct WasmtimeInstance {
77     store: Store<StoreLimits>,
78     instance: Instance,
79 }
80 
81 impl WasmtimeInstance {
82     /// Instantiate a new Wasmtime instance.
83     pub fn new(mut store: Store<StoreLimits>, module: Module) -> Result<Self> {
84         let instance = dummy::dummy_linker(&mut store, &module)
85             .and_then(|l| l.instantiate(&mut store, &module))
86             .context("unable to instantiate module in wasmtime")?;
87         Ok(Self { store, instance })
88     }
89 
90     /// Retrieve the names and types of all exported functions in the instance.
91     ///
92     /// This is useful for evaluating each exported function with different
93     /// values. The [`DiffInstance`] trait asks for the function name and we
94     /// need to know the function signature in order to pass in the right
95     /// arguments.
96     pub fn exported_functions(&mut self) -> Vec<(String, FuncType)> {
97         let exported_functions = self
98             .instance
99             .exports(&mut self.store)
100             .map(|e| (e.name().to_owned(), e.into_func()))
101             .filter_map(|(n, f)| f.map(|f| (n, f)))
102             .collect::<Vec<_>>();
103         exported_functions
104             .into_iter()
105             .map(|(n, f)| (n, f.ty(&self.store)))
106             .collect()
107     }
108 
109     /// Returns the list of globals and their types exported from this instance.
110     pub fn exported_globals(&mut self) -> Vec<(String, DiffValueType)> {
111         let globals = self
112             .instance
113             .exports(&mut self.store)
114             .filter_map(|e| {
115                 let name = e.name();
116                 e.into_global().map(|g| (name.to_string(), g))
117             })
118             .collect::<Vec<_>>();
119 
120         globals
121             .into_iter()
122             .map(|(name, global)| {
123                 (
124                     name,
125                     global.ty(&self.store).content().clone().try_into().unwrap(),
126                 )
127             })
128             .collect()
129     }
130 
131     /// Returns the list of exported memories and whether or not it's a shared
132     /// memory.
133     pub fn exported_memories(&mut self) -> Vec<(String, bool)> {
134         self.instance
135             .exports(&mut self.store)
136             .filter_map(|e| {
137                 let name = e.name();
138                 match e.into_extern() {
139                     Extern::Memory(_) => Some((name.to_string(), false)),
140                     Extern::SharedMemory(_) => Some((name.to_string(), true)),
141                     _ => None,
142                 }
143             })
144             .collect()
145     }
146 
147     /// Returns whether or not this instance has hit its OOM condition yet.
148     pub fn is_oom(&self) -> bool {
149         self.store.data().is_oom()
150     }
151 }
152 
153 impl DiffInstance for WasmtimeInstance {
154     fn name(&self) -> &'static str {
155         "wasmtime"
156     }
157 
158     fn evaluate(
159         &mut self,
160         function_name: &str,
161         arguments: &[DiffValue],
162         _results: &[DiffValueType],
163     ) -> Result<Option<Vec<DiffValue>>> {
164         let arguments: Vec<_> = arguments.iter().map(Val::from).collect();
165 
166         let function = self
167             .instance
168             .get_func(&mut self.store, function_name)
169             .expect("unable to access exported function");
170         let ty = function.ty(&self.store);
171         let mut results = vec![Val::I32(0); ty.results().len()];
172         function.call(&mut self.store, &arguments, &mut results)?;
173 
174         let results = results.into_iter().map(Val::into).collect();
175         Ok(Some(results))
176     }
177 
178     fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
179         Some(
180             self.instance
181                 .get_global(&mut self.store, name)
182                 .unwrap()
183                 .get(&mut self.store)
184                 .into(),
185         )
186     }
187 
188     fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
189         Some(if shared {
190             let memory = self
191                 .instance
192                 .get_shared_memory(&mut self.store, name)
193                 .unwrap();
194             memory.data().iter().map(|i| unsafe { *i.get() }).collect()
195         } else {
196             self.instance
197                 .get_memory(&mut self.store, name)
198                 .unwrap()
199                 .data(&self.store)
200                 .to_vec()
201         })
202     }
203 }
204 
205 impl From<&DiffValue> for Val {
206     fn from(v: &DiffValue) -> Self {
207         match *v {
208             DiffValue::I32(n) => Val::I32(n),
209             DiffValue::I64(n) => Val::I64(n),
210             DiffValue::F32(n) => Val::F32(n),
211             DiffValue::F64(n) => Val::F64(n),
212             DiffValue::V128(n) => Val::V128(n.into()),
213             DiffValue::FuncRef { null } => {
214                 assert!(null);
215                 Val::FuncRef(None)
216             }
217             DiffValue::ExternRef { null } => {
218                 assert!(null);
219                 Val::ExternRef(None)
220             }
221             DiffValue::AnyRef { null } => {
222                 assert!(null);
223                 Val::AnyRef(None)
224             }
225         }
226     }
227 }
228 
229 impl Into<DiffValue> for Val {
230     fn into(self) -> DiffValue {
231         match self {
232             Val::I32(n) => DiffValue::I32(n),
233             Val::I64(n) => DiffValue::I64(n),
234             Val::F32(n) => DiffValue::F32(n),
235             Val::F64(n) => DiffValue::F64(n),
236             Val::V128(n) => DiffValue::V128(n.into()),
237             Val::ExternRef(r) => DiffValue::ExternRef { null: r.is_none() },
238             Val::FuncRef(r) => DiffValue::FuncRef { null: r.is_none() },
239             Val::AnyRef(r) => DiffValue::AnyRef { null: r.is_none() },
240         }
241     }
242 }
243 
244 #[test]
245 fn smoke_cranelift() {
246     crate::oracles::engine::smoke_test_engine(|u, config| {
247         WasmtimeEngine::new(u, config, CompilerStrategy::Cranelift)
248     })
249 }
250 
251 #[test]
252 fn smoke_winch() {
253     if !cfg!(target_arch = "x86_64") {
254         return;
255     }
256     crate::oracles::engine::smoke_test_engine(|u, config| {
257         WasmtimeEngine::new(u, config, CompilerStrategy::Winch)
258     })
259 }
260