wasm_smith/core/
terminate.rs1use super::*;
2use anyhow::{Result, bail};
3
4impl Module {
5 pub fn ensure_termination(&mut self, default_fuel: u32) -> Result<u32> {
22 let fuel_global = self.globals.len() as u32;
23 self.globals.push(GlobalType {
24 val_type: ValType::I32,
25 mutable: true,
26 shared: false,
27 });
28 self.defined_globals
29 .push((fuel_global, ConstExpr::i32_const(default_fuel as i32)));
30
31 let defined_funcs = &self.funcs[self.funcs.len() - self.code.len()..];
32 for ((_, ty), code) in defined_funcs.iter().zip(self.code.iter_mut()) {
33 let params = ty.params.len();
34
35 let mut temp_i32_local = None;
36 let mut temp_i32_local = |locals: &mut Vec<_>| {
37 if let Some(l) = temp_i32_local {
38 return l;
39 }
40 let l = u32::try_from(locals.len() + params).unwrap();
41 locals.push(ValType::I32);
42 temp_i32_local = Some(l);
43 l
44 };
45
46 let mut temp_i64_local = None;
47 let mut temp_i64_local = |locals: &mut Vec<_>| {
48 if let Some(l) = temp_i64_local {
49 return l;
50 }
51 let l = u32::try_from(locals.len() + params).unwrap();
52 locals.push(ValType::I64);
53 temp_i64_local = Some(l);
54 l
55 };
56
57 let check_fuel_1 = |insts: &mut Vec<Instruction>| {
58 insts.push(Instruction::GlobalGet(fuel_global));
60 insts.push(Instruction::I32Eqz);
61 insts.push(Instruction::If(BlockType::Empty));
62 insts.push(Instruction::Unreachable);
63 insts.push(Instruction::End);
64
65 insts.push(Instruction::GlobalGet(fuel_global));
67 insts.push(Instruction::I32Const(1));
68 insts.push(Instruction::I32Sub);
69 insts.push(Instruction::GlobalSet(fuel_global));
70 };
71
72 let check_fuel_n = |insts: &mut Vec<Instruction>, local: u32| {
73 insts.push(Instruction::LocalGet(local));
75 insts.push(Instruction::GlobalGet(fuel_global));
76 insts.push(Instruction::I32GeU);
77 insts.push(Instruction::If(BlockType::Empty));
78 insts.push(Instruction::Unreachable);
79 insts.push(Instruction::End);
80 insts.push(Instruction::GlobalGet(fuel_global));
82 insts.push(Instruction::LocalGet(local));
83 insts.push(Instruction::I32Sub);
84 insts.push(Instruction::GlobalSet(fuel_global));
85 };
86
87 let check_fuel_32_or_64 = |locals: &mut Vec<ValType>,
88 temp_i32_local: &mut dyn FnMut(&mut Vec<ValType>) -> u32,
89 temp_i64_local: &mut dyn FnMut(&mut Vec<ValType>) -> u32,
90 new_insts: &mut Vec<Instruction>,
91 inst: Instruction,
92 is_64: bool| {
93 if is_64 {
94 let local64 = temp_i64_local(locals);
95 let local32 = temp_i32_local(locals);
96 new_insts.push(Instruction::LocalTee(local64));
97 new_insts.push(Instruction::I32WrapI64);
98 new_insts.push(Instruction::LocalSet(local32));
99 check_fuel_n(new_insts, local32);
100 new_insts.push(Instruction::LocalGet(local64));
101 new_insts.push(inst);
102 } else {
103 let local = temp_i32_local(locals);
104 new_insts.push(Instruction::LocalTee(local));
105 check_fuel_n(new_insts, local);
106 new_insts.push(inst);
107 }
108 };
109
110 let instrs = match &mut code.instructions {
111 Instructions::Generated(list) => list,
112 Instructions::Arbitrary(_) => {
113 bail!(
114 "failed to ensure that a function generated due to it \
115 containing arbitrary instructions"
116 )
117 }
118 };
119 let mut new_insts = Vec::with_capacity(instrs.len() * 2);
120
121 check_fuel_1(&mut new_insts);
124
125 for inst in mem::replace(instrs, vec![]) {
126 match &inst {
127 Instruction::Loop(_) => {
129 new_insts.push(inst);
130 check_fuel_1(&mut new_insts);
131 }
132
133 Instruction::ArrayCopy { .. }
137 | Instruction::ArrayFill(_)
138 | Instruction::ArrayInitData { .. }
139 | Instruction::ArrayInitElem { .. }
140 | Instruction::ArrayNew(_)
141 | Instruction::ArrayNewDefault(_)
142 | Instruction::ArrayNewData { .. }
143 | Instruction::ArrayNewElem { .. } => {
144 let local = temp_i32_local(&mut code.locals);
145 new_insts.push(Instruction::LocalTee(local));
146 check_fuel_n(&mut new_insts, local);
147 new_insts.push(inst);
148 }
149
150 Instruction::TableInit { .. } => {
153 let local = temp_i32_local(&mut code.locals);
154 new_insts.push(Instruction::LocalTee(local));
155 check_fuel_n(&mut new_insts, local);
156 new_insts.push(inst);
157 }
158
159 Instruction::TableFill(table) => {
162 let table = usize::try_from(*table).unwrap();
163 let is_64 = self.tables[table].table64;
164 check_fuel_32_or_64(
165 &mut code.locals,
166 &mut temp_i32_local,
167 &mut temp_i64_local,
168 &mut new_insts,
169 inst,
170 is_64,
171 );
172 }
173
174 Instruction::TableCopy {
177 dst_table,
178 src_table,
179 } => {
180 let dst_table = usize::try_from(*dst_table).unwrap();
181 let src_table = usize::try_from(*src_table).unwrap();
182 check_fuel_32_or_64(
183 &mut code.locals,
184 &mut temp_i32_local,
185 &mut temp_i64_local,
186 &mut new_insts,
187 inst,
188 self.tables[dst_table].table64 && self.tables[src_table].table64,
189 );
190 }
191
192 Instruction::MemoryInit { .. } => {
195 let local = temp_i32_local(&mut code.locals);
196 new_insts.push(Instruction::LocalTee(local));
197 check_fuel_n(&mut new_insts, local);
198 new_insts.push(inst);
199 }
200
201 Instruction::MemoryFill(mem) => {
204 let mem = usize::try_from(*mem).unwrap();
205 check_fuel_32_or_64(
206 &mut code.locals,
207 &mut temp_i32_local,
208 &mut temp_i64_local,
209 &mut new_insts,
210 inst,
211 self.memories[mem].memory64,
212 );
213 }
214
215 Instruction::MemoryCopy { dst_mem, src_mem } => {
219 let dst_mem = usize::try_from(*dst_mem).unwrap();
220 let src_mem = usize::try_from(*src_mem).unwrap();
221 check_fuel_32_or_64(
222 &mut code.locals,
223 &mut temp_i32_local,
224 &mut temp_i64_local,
225 &mut new_insts,
226 inst,
227 self.memories[dst_mem].memory64 && self.memories[src_mem].memory64,
228 );
229 }
230
231 _ => new_insts.push(inst),
233 }
234 }
235
236 *instrs = new_insts;
237 }
238
239 Ok(fuel_global)
240 }
241}