xref: /wasmtime-44.0.1/tests/all/fuel.rs (revision e50d897b)
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