1 use wasmtime::*;
2 use wasmtime_test_macros::wasmtime_test;
3 use wast::parser::{self, Parse, ParseBuffer, Parser};
4 use wast::token::Span;
5
6 mod kw {
7 wast::custom_keyword!(assert_fuel);
8 }
9
10 struct FuelWast<'a> {
11 assertions: Vec<(Span, u64, wast::core::Module<'a>)>,
12 }
13
14 impl<'a> Parse<'a> for FuelWast<'a> {
parse(parser: Parser<'a>) -> parser::Result<Self>15 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
16 let mut assertions = Vec::new();
17 while !parser.is_empty() {
18 assertions.push(parser.parens(|p| {
19 let span = p.parse::<kw::assert_fuel>()?.0;
20 Ok((span, p.parse()?, p.parens(|p| p.parse())?))
21 })?);
22 }
23 Ok(FuelWast { assertions })
24 }
25 }
26
27 #[wasmtime_test]
28 #[cfg_attr(miri, ignore)]
run(config: &mut Config) -> Result<()>29 fn run(config: &mut Config) -> Result<()> {
30 config.consume_fuel(true);
31 let test = std::fs::read_to_string("tests/all/fuel.wast")?;
32 let buf = ParseBuffer::new(&test)?;
33 let mut wast = parser::parse::<FuelWast<'_>>(&buf)?;
34 for (span, fuel, module) in wast.assertions.iter_mut() {
35 let consumed = fuel_consumed(&config, &module.encode()?)?;
36 if consumed == *fuel {
37 continue;
38 }
39 let (line, col) = span.linecol_in(&test);
40 panic!(
41 "tests/all/fuel.wast:{}:{} - expected {} fuel, found {}",
42 line + 1,
43 col + 1,
44 fuel,
45 consumed
46 );
47 }
48 Ok(())
49 }
50
fuel_consumed(config: &Config, wasm: &[u8]) -> Result<u64>51 fn fuel_consumed(config: &Config, wasm: &[u8]) -> Result<u64> {
52 let engine = Engine::new(&config)?;
53 let module = Module::new(&engine, wasm)?;
54 let mut store = Store::new(&engine, ());
55 store.set_fuel(u64::MAX)?;
56 drop(Instance::new(&mut store, &module, &[]));
57 Ok(u64::MAX - store.get_fuel()?)
58 }
59
60 #[wasmtime_test(wasm_features(gc, function_references, bulk_memory))]
61 #[cfg_attr(miri, ignore)]
iloop(config: &mut Config) -> Result<()>62 fn iloop(config: &mut Config) -> Result<()> {
63 config.consume_fuel(true);
64 iloop_aborts(
65 &config,
66 r#"
67 (module
68 (start 0)
69 (func loop br 0 end)
70 )
71 "#,
72 )?;
73 iloop_aborts(
74 &config,
75 r#"
76 (module
77 (start 0)
78 (func loop i32.const 1 br_if 0 end)
79 )
80 "#,
81 )?;
82 iloop_aborts(
83 &config,
84 r#"
85 (module
86 (start 0)
87 (func loop i32.const 0 br_table 0 end)
88 )
89 "#,
90 )?;
91 iloop_aborts(
92 &config,
93 r#"
94 (module
95 (start 0)
96 (func $f0 call $f1 call $f1)
97 (func $f1 call $f2 call $f2)
98 (func $f2 call $f3 call $f3)
99 (func $f3 call $f4 call $f4)
100 (func $f4 call $f5 call $f5)
101 (func $f5 call $f6 call $f6)
102 (func $f6 call $f7 call $f7)
103 (func $f7 call $f8 call $f8)
104 (func $f8 call $f9 call $f9)
105 (func $f9 call $f10 call $f10)
106 (func $f10 call $f11 call $f11)
107 (func $f11 call $f12 call $f12)
108 (func $f12 call $f13 call $f13)
109 (func $f13 call $f14 call $f14)
110 (func $f14 call $f15 call $f15)
111 (func $f15 call $f16 call $f16)
112 (func $f16)
113 )
114 "#,
115 )?;
116 iloop_aborts(
117 &config,
118 r#"
119 (module
120 (start 0)
121 (func loop ref.null func br_on_null 0 drop end)
122 )
123 "#,
124 )?;
125 iloop_aborts(
126 &config,
127 r#"
128 (module
129 (start 0)
130 (func
131 ref.func 0
132 loop (param (ref func))
133 br_on_non_null 0
134 unreachable
135 end
136 )
137 (elem declare func 0)
138 )
139 "#,
140 )?;
141 iloop_aborts(
142 &config,
143 r#"
144 (module
145 (start 0)
146 (func
147 i32.const 0
148 ref.i31
149 loop (param (ref i31))
150 br_on_cast 0 anyref (ref i31)
151 unreachable
152 end
153 )
154 (elem declare func 0)
155 )
156 "#,
157 )?;
158 iloop_aborts(
159 &config,
160 r#"
161 (module
162 (start 0)
163 (func
164 ref.null any
165 loop (param anyref)
166 br_on_cast_fail 0 anyref (ref i31)
167 unreachable
168 end
169 )
170 (elem declare func 0)
171 )
172 "#,
173 )?;
174 iloop_aborts(
175 &config,
176 r#"
177 (module
178 (type $a (array i8))
179 (start 0)
180 (func
181 i32.const 0x400_0000
182 array.new_default $a
183 drop
184 )
185 )
186 "#,
187 )?;
188
189 fn iloop_aborts(config: &Config, wat: &str) -> Result<()> {
190 let engine = Engine::new(&config)?;
191 let module = Module::new(&engine, wat)?;
192 let mut store = Store::new(&engine, ());
193 store.set_fuel(10_000)?;
194 let error = Instance::new(&mut store, &module, &[]).err().unwrap();
195 assert_eq!(error.downcast::<Trap>().unwrap(), Trap::OutOfFuel);
196 Ok(())
197 }
198
199 Ok(())
200 }
201
202 #[wasmtime_test]
manual_fuel(config: &mut Config) -> Result<()>203 fn manual_fuel(config: &mut Config) -> Result<()> {
204 config.consume_fuel(true);
205 let engine = Engine::new(&config)?;
206 let mut store = Store::new(&engine, ());
207 store.set_fuel(10_000).unwrap();
208 assert_eq!(store.get_fuel().ok(), Some(10_000));
209 assert_eq!(store.set_fuel(1).ok(), Some(()));
210 assert_eq!(store.get_fuel().ok(), Some(1));
211 Ok(())
212 }
213
214 #[wasmtime_test]
215 #[cfg_attr(miri, ignore)]
host_function_consumes_all(config: &mut Config) -> Result<()>216 fn host_function_consumes_all(config: &mut Config) -> Result<()> {
217 const FUEL: u64 = 10_000;
218 config.consume_fuel(true);
219 let engine = Engine::new(&config)?;
220 let module = Module::new(
221 &engine,
222 r#"
223 (module
224 (import "" "" (func))
225 (func (export "")
226 call 0
227 call $other)
228 (func $other))
229 "#,
230 )
231 .unwrap();
232 let mut store = Store::new(&engine, ());
233 store.set_fuel(FUEL).unwrap();
234 let func = Func::wrap(&mut store, |mut caller: Caller<'_, ()>| {
235 let remaining = caller.get_fuel().unwrap();
236 assert_eq!(remaining, FUEL - 2);
237 assert!(caller.set_fuel(1).is_ok());
238 });
239
240 let instance = Instance::new(&mut store, &module, &[func.into()]).unwrap();
241 let export = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
242 let trap = export.call(&mut store, ()).unwrap_err();
243 assert_eq!(trap.downcast::<Trap>().unwrap(), Trap::OutOfFuel);
244 Ok(())
245 }
246
247 #[wasmtime_test]
manual_edge_cases(config: &mut Config) -> Result<()>248 fn manual_edge_cases(config: &mut Config) -> Result<()> {
249 config.consume_fuel(true);
250 let engine = Engine::new(&config)?;
251 let mut store = Store::new(&engine, ());
252 store.set_fuel(u64::MAX).unwrap();
253 assert_eq!(store.get_fuel().unwrap(), u64::MAX);
254 Ok(())
255 }
256
257 #[wasmtime_test]
258 #[cfg_attr(miri, ignore)]
unconditionally_trapping_memory_accesses_save_fuel_before_trapping( config: &mut Config, ) -> Result<()>259 fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping(
260 config: &mut Config,
261 ) -> Result<()> {
262 config.consume_fuel(true);
263 config.memory_reservation(0x1_0000);
264
265 let engine = Engine::new(&config)?;
266
267 let module = Module::new(
268 &engine,
269 r#"
270 (module
271 (memory 1 1)
272 (func (export "f") (param i32) (result i32)
273 local.get 0
274 local.get 0
275 i32.add
276 ;; This offset is larger than our memory max size and therefore
277 ;; will unconditionally trap.
278 i32.load8_s offset=0xffffffff))
279 "#,
280 )
281 .unwrap();
282
283 let mut store = Store::new(&engine, ());
284 let init_fuel = 1_000;
285 store.set_fuel(init_fuel).unwrap();
286 assert_eq!(init_fuel, store.get_fuel().unwrap());
287
288 let instance = Instance::new(&mut store, &module, &[]).unwrap();
289 let f = instance
290 .get_typed_func::<i32, i32>(&mut store, "f")
291 .unwrap();
292
293 let trap = f.call(&mut store, 0).unwrap_err();
294 assert_eq!(trap.downcast::<Trap>().unwrap(), Trap::MemoryOutOfBounds);
295
296 // The `i32.add` consumed some fuel before the unconditionally trapping
297 // memory access.
298 let consumed_fuel = init_fuel - store.get_fuel().unwrap();
299 assert!(consumed_fuel > 0);
300 Ok(())
301 }
302
303 #[wasmtime_test]
304 #[cfg_attr(miri, ignore)]
get_fuel_clamps_at_zero(config: &mut Config) -> Result<()>305 fn get_fuel_clamps_at_zero(config: &mut Config) -> Result<()> {
306 config.consume_fuel(true);
307 let engine = Engine::new(config)?;
308 let mut store = Store::new(&engine, ());
309 let module = Module::new(
310 &engine,
311 r#"
312 (module
313 (func $add2 (export "add2") (param $n i32) (result i32)
314 (i32.add (local.get $n) (i32.const 2))
315 )
316 )
317 "#,
318 )?;
319 let instance = Instance::new(&mut store, &module, &[])?;
320
321 let add2 = instance.get_typed_func::<i32, i32>(&mut store, "add2")?;
322
323 // Start with 6 fuel and one invocation of this function should cost 4 fuel
324 store.set_fuel(6)?;
325 assert_eq!(store.get_fuel()?, 6);
326 add2.call(&mut store, 10)?;
327 assert_eq!(store.get_fuel()?, 2);
328
329 // One more invocation of the function would technically take us to -2 fuel,
330 // but that's not representable, so the store should report 0 fuel after
331 // this completes.
332 add2.call(&mut store, 10)?;
333 assert_eq!(store.get_fuel()?, 0);
334
335 // Any further attempts should fail.
336 assert!(add2.call(&mut store, 10).is_err());
337
338 Ok(())
339 }
340
341 #[wasmtime_test]
342 #[cfg_attr(miri, ignore)]
immediate_trap_with_fuel1(config: &mut Config) -> Result<()>343 fn immediate_trap_with_fuel1(config: &mut Config) -> Result<()> {
344 config.consume_fuel(true);
345 let engine = Engine::new(config)?;
346 let mut store = Store::new(&engine, ());
347
348 let module = Module::new(
349 &engine,
350 r#"
351 (module
352 (func (export "main"))
353 )
354 "#,
355 )?;
356
357 let instance = Instance::new(&mut store, &module, &[])?;
358 let main = instance.get_typed_func::<(), ()>(&mut store, "main")?;
359 store.set_fuel(1)?;
360
361 assert!(main.call(&mut store, ()).is_err());
362
363 Ok(())
364 }
365
366 #[wasmtime_test(strategies(only(Winch)))]
367 #[cfg_attr(miri, ignore)]
ensure_stack_alignment(config: &mut Config) -> Result<()>368 fn ensure_stack_alignment(config: &mut Config) -> Result<()> {
369 config.consume_fuel(true);
370 let engine = Engine::new(config)?;
371 let mut store = Store::new(&engine, ());
372 store.set_fuel(100000000)?;
373
374 let bytes = include_bytes!("../misc_testsuite/winch/fuel_stack_alignment.wat");
375 let module = Module::new(&engine, bytes)?;
376 let instance = Instance::new(&mut store, &module, &[])?;
377 let func = instance.get_typed_func::<f32, ()>(&mut store, "")?;
378 let trap = func.call(&mut store, 50397184.0).unwrap_err();
379 assert_eq!(
380 trap.downcast::<Trap>().unwrap(),
381 Trap::UnreachableCodeReached
382 );
383 Ok(())
384 }
385
386 #[wasmtime_test]
387 #[cfg_attr(miri, ignore)]
custom_operator_cost(config: &mut Config) -> Result<()>388 fn custom_operator_cost(config: &mut Config) -> Result<()> {
389 config.consume_fuel(true);
390 let op_cost = OperatorCost {
391 I32Const: 12,
392 I32Add: 23,
393 I64Const: 64,
394 I64Add: 128,
395 Drop: 5,
396 ..Default::default()
397 };
398 config.operator_cost(op_cost.clone());
399 let engine = Engine::new(config)?;
400 let module = Module::new(
401 &engine,
402 r#"
403 (module
404 (func (export "main")
405 ;; i32: 1 + 2
406 (drop (i32.add (i32.const 1) (i32.const 2)))
407
408 ;; i64: 3 + 4
409 (drop (i64.add (i64.const 3) (i64.const 4)))
410 )
411 )
412 "#,
413 )?;
414 let mut store = Store::new(&engine, ());
415 store.set_fuel(10_000)?;
416
417 let instance = Instance::new(&mut store, &module, &[])?;
418 let main = instance.get_typed_func::<(), ()>(&mut store, "main")?;
419
420 let initial_fuel = store.get_fuel()?;
421 main.call(&mut store, ())?;
422 let cost_of_execution = u64::from(op_cost.I32Add)
423 + u64::from(op_cost.I64Add)
424 + u64::from(op_cost.I32Const) * 2
425 + u64::from(op_cost.I64Const) * 2
426 + u64::from(op_cost.Drop) * 2
427 + 1;
428 assert_eq!(store.get_fuel()?, initial_fuel - cost_of_execution);
429
430 Ok(())
431 }
432