wasm_smith/core/terminate.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
use super::*;
use anyhow::{bail, Result};
impl Module {
/// Ensure that all of this Wasm module's functions will terminate when
/// executed.
///
/// This adds a new mutable, exported global to the module to keep track of
/// how much "fuel" is left. Fuel is decremented at the head of each loop
/// and function. When fuel reaches zero, a trap is raised.
///
/// The index of the fuel global is returned, so that you may control how
/// much fuel the module is given.
///
/// # Errors
///
/// Returns an error if any function body was generated with
/// possibly-invalid bytes rather than being generated by wasm-smith. In
/// such a situation this pass does not parse the input bytes and inject
/// instructions, instead it returns an error.
pub fn ensure_termination(&mut self, default_fuel: u32) -> Result<u32> {
let fuel_global = self.globals.len() as u32;
self.globals.push(GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
});
self.defined_globals
.push((fuel_global, ConstExpr::i32_const(default_fuel as i32)));
for code in &mut self.code {
let check_fuel = |insts: &mut Vec<Instruction>| {
// if fuel == 0 { trap }
insts.push(Instruction::GlobalGet(fuel_global));
insts.push(Instruction::I32Eqz);
insts.push(Instruction::If(BlockType::Empty));
insts.push(Instruction::Unreachable);
insts.push(Instruction::End);
// fuel -= 1
insts.push(Instruction::GlobalGet(fuel_global));
insts.push(Instruction::I32Const(1));
insts.push(Instruction::I32Sub);
insts.push(Instruction::GlobalSet(fuel_global));
};
let instrs = match &mut code.instructions {
Instructions::Generated(list) => list,
Instructions::Arbitrary(_) => {
bail!(
"failed to ensure that a function generated due to it \
containing arbitrary instructions"
)
}
};
let mut new_insts = Vec::with_capacity(instrs.len() * 2);
// Check fuel at the start of functions to deal with infinite
// recursion.
check_fuel(&mut new_insts);
for inst in mem::replace(instrs, vec![]) {
let is_loop = matches!(&inst, Instruction::Loop(_));
new_insts.push(inst);
// Check fuel at loop heads to deal with infinite loops.
if is_loop {
check_fuel(&mut new_insts);
}
}
*instrs = new_insts;
}
Ok(fuel_global)
}
}