You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

225 lines
6.9 KiB

use anyhow::Result;
use wasmtime::*;
use wast::parser::{self, Parse, ParseBuffer, Parser};
use wast::token::Span;
mod kw {
wast::custom_keyword!(assert_fuel);
}
struct FuelWast<'a> {
assertions: Vec<(Span, u64, wast::core::Module<'a>)>,
}
impl<'a> Parse<'a> for FuelWast<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
let mut assertions = Vec::new();
while !parser.is_empty() {
assertions.push(parser.parens(|p| {
let span = p.parse::<kw::assert_fuel>()?.0;
Ok((span, p.parse()?, p.parens(|p| p.parse())?))
})?);
}
Ok(FuelWast { assertions })
}
}
#[test]
fn run() -> Result<()> {
let test = std::fs::read_to_string("tests/all/fuel.wast")?;
let buf = ParseBuffer::new(&test)?;
let mut wast = parser::parse::<FuelWast<'_>>(&buf)?;
for (span, fuel, module) in wast.assertions.iter_mut() {
let consumed = fuel_consumed(&module.encode()?);
if consumed == *fuel {
continue;
}
let (line, col) = span.linecol_in(&test);
panic!(
"tests/all/fuel.wast:{}:{} - expected {} fuel, found {}",
line + 1,
col + 1,
fuel,
consumed
);
}
Ok(())
}
fn fuel_consumed(wasm: &[u8]) -> u64 {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config).unwrap();
let module = Module::new(&engine, wasm).unwrap();
let mut store = Store::new(&engine, ());
store.add_fuel(u64::max_value()).unwrap();
drop(Instance::new(&mut store, &module, &[]));
store.fuel_consumed().unwrap()
}
#[test]
fn iloop() {
iloop_aborts(
r#"
(module
(start 0)
(func loop br 0 end)
)
"#,
);
iloop_aborts(
r#"
(module
(start 0)
(func loop i32.const 1 br_if 0 end)
)
"#,
);
iloop_aborts(
r#"
(module
(start 0)
(func loop i32.const 0 br_table 0 end)
)
"#,
);
iloop_aborts(
r#"
(module
(start 0)
(func $f0 call $f1 call $f1)
(func $f1 call $f2 call $f2)
(func $f2 call $f3 call $f3)
(func $f3 call $f4 call $f4)
(func $f4 call $f5 call $f5)
(func $f5 call $f6 call $f6)
(func $f6 call $f7 call $f7)
(func $f7 call $f8 call $f8)
(func $f8 call $f9 call $f9)
(func $f9 call $f10 call $f10)
(func $f10 call $f11 call $f11)
(func $f11 call $f12 call $f12)
(func $f12 call $f13 call $f13)
(func $f13 call $f14 call $f14)
(func $f14 call $f15 call $f15)
(func $f15 call $f16 call $f16)
(func $f16)
)
"#,
);
fn iloop_aborts(wat: &str) {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config).unwrap();
let module = Module::new(&engine, wat).unwrap();
let mut store = Store::new(&engine, ());
store.add_fuel(10_000).unwrap();
let error = Instance::new(&mut store, &module, &[]).err().unwrap();
assert_eq!(error.downcast::<Trap>().unwrap(), Trap::OutOfFuel);
}
}
#[test]
fn manual_fuel() {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config).unwrap();
let mut store = Store::new(&engine, ());
store.add_fuel(10_000).unwrap();
assert_eq!(store.fuel_consumed(), Some(0));
assert_eq!(store.consume_fuel(1).unwrap(), 9_999);
assert_eq!(store.fuel_consumed(), Some(1));
assert!(store.consume_fuel(10_000).is_err());
assert_eq!(store.consume_fuel(999).unwrap(), 9_000);
assert!(store.consume_fuel(10_000).is_err());
assert_eq!(store.consume_fuel(8998).unwrap(), 2);
assert!(store.consume_fuel(3).is_err());
assert_eq!(store.consume_fuel(1).unwrap(), 1);
assert_eq!(store.consume_fuel(1).unwrap(), 0);
assert_eq!(store.consume_fuel(0).unwrap(), 0);
}
#[test]
fn host_function_consumes_all() {
const FUEL: u64 = 10_000;
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config).unwrap();
let module = Module::new(
&engine,
r#"
(module
(import "" "" (func))
(func (export "")
call 0
call $other)
(func $other))
"#,
)
.unwrap();
let mut store = Store::new(&engine, ());
store.add_fuel(FUEL).unwrap();
let func = Func::wrap(&mut store, |mut caller: Caller<'_, ()>| {
let consumed = caller.fuel_consumed().unwrap();
assert_eq!(caller.consume_fuel((FUEL - consumed) - 1).unwrap(), 1);
});
let instance = Instance::new(&mut store, &module, &[func.into()]).unwrap();
let export = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
let trap = export.call(&mut store, ()).unwrap_err();
assert_eq!(trap.downcast::<Trap>().unwrap(), Trap::OutOfFuel);
}
#[test]
fn manual_edge_cases() {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config).unwrap();
let mut store = Store::new(&engine, ());
store.add_fuel(u64::MAX).unwrap();
assert_eq!(store.fuel_consumed(), Some(0));
assert!(store.consume_fuel(u64::MAX).is_err());
assert!(store.consume_fuel(i64::MAX as u64 + 1).is_err());
assert_eq!(store.consume_fuel(i64::MAX as u64).unwrap(), 0);
}
#[test]
fn unconditionally_trapping_memory_accesses_save_fuel_before_trapping() {
let mut config = Config::new();
config.consume_fuel(true);
config.static_memory_maximum_size(0x1_0000);
let engine = Engine::new(&config).unwrap();
let module = Module::new(
&engine,
r#"
(module
(memory 1 1)
(func (export "f") (param i32) (result i32)
local.get 0
local.get 0
i32.add
;; This offset is larger than our memory max size and therefore
;; will unconditionally trap.
i32.load8_s offset=0xffffffff))
"#,
)
.unwrap();
let mut store = Store::new(&engine, ());
store.add_fuel(1_000).unwrap();
let instance = Instance::new(&mut store, &module, &[]).unwrap();
let f = instance
.get_typed_func::<i32, i32>(&mut store, "f")
.unwrap();
let trap = f.call(&mut store, 0).unwrap_err();
assert_eq!(trap.downcast::<Trap>().unwrap(), Trap::MemoryOutOfBounds);
// The `i32.add` consumed some fuel before the unconditionally trapping
// memory access.
assert!(store.fuel_consumed().unwrap() > 0);
}