/src/wasm-tools/crates/wasmparser/src/validator/func.rs
Line | Count | Source |
1 | | use super::operators::{Frame, OperatorValidator, OperatorValidatorAllocations}; |
2 | | use crate::{BinaryReader, Result, ValType, VisitOperator}; |
3 | | use crate::{FrameStack, FunctionBody, ModuleArity, Operator, WasmFeatures, WasmModuleResources}; |
4 | | |
5 | | /// Resources necessary to perform validation of a function. |
6 | | /// |
7 | | /// This structure is created by |
8 | | /// [`Validator::code_section_entry`](crate::Validator::code_section_entry) and |
9 | | /// is created per-function in a WebAssembly module. This structure is suitable |
10 | | /// for sending to other threads while the original |
11 | | /// [`Validator`](crate::Validator) continues processing other functions. |
12 | | #[derive(Debug)] |
13 | | pub struct FuncToValidate<T> { |
14 | | /// Reusable, heap allocated resources to drive the Wasm validation. |
15 | | pub resources: T, |
16 | | /// The core Wasm function index being validated. |
17 | | pub index: u32, |
18 | | /// The core Wasm type index of the function being validated, |
19 | | /// defining the results and parameters to the function. |
20 | | pub ty: u32, |
21 | | /// The Wasm features enabled to validate the function. |
22 | | pub features: WasmFeatures, |
23 | | } |
24 | | |
25 | | impl<T: WasmModuleResources> FuncToValidate<T> { |
26 | | /// Converts this [`FuncToValidate`] into a [`FuncValidator`] using the |
27 | | /// `allocs` provided. |
28 | | /// |
29 | | /// This method, in conjunction with [`FuncValidator::into_allocations`], |
30 | | /// provides a means to reuse allocations across validation of each |
31 | | /// individual function. Note that it is also sufficient to call this |
32 | | /// method with `Default::default()` if no prior allocations are |
33 | | /// available. |
34 | | /// |
35 | | /// # Panics |
36 | | /// |
37 | | /// If a `FuncToValidate` was created with an invalid `ty` index then this |
38 | | /// function will panic. |
39 | 226k | pub fn into_validator(self, allocs: FuncValidatorAllocations) -> FuncValidator<T> { |
40 | | let FuncToValidate { |
41 | 226k | resources, |
42 | 226k | index, |
43 | 226k | ty, |
44 | 226k | features, |
45 | 226k | } = self; |
46 | 226k | let validator = |
47 | 226k | OperatorValidator::new_func(ty, 0, &features, &resources, allocs.0).unwrap(); |
48 | 226k | FuncValidator { |
49 | 226k | validator, |
50 | 226k | resources, |
51 | 226k | index, |
52 | 226k | } |
53 | 226k | } |
54 | | } |
55 | | |
56 | | /// Validation context for a WebAssembly function. |
57 | | /// |
58 | | /// This is a finalized validator which is ready to process a [`FunctionBody`]. |
59 | | /// This is created from the [`FuncToValidate::into_validator`] method. |
60 | | #[derive(Clone)] |
61 | | pub struct FuncValidator<T> { |
62 | | validator: OperatorValidator, |
63 | | resources: T, |
64 | | index: u32, |
65 | | } |
66 | | |
67 | | impl<T: WasmModuleResources> ModuleArity for FuncValidator<T> { |
68 | 0 | fn sub_type_at(&self, type_idx: u32) -> Option<&crate::SubType> { |
69 | 0 | self.resources.sub_type_at(type_idx) |
70 | 0 | } |
71 | | |
72 | 0 | fn tag_type_arity(&self, at: u32) -> Option<(u32, u32)> { |
73 | 0 | let ty = self.resources.tag_at(at)?; |
74 | 0 | Some(( |
75 | 0 | u32::try_from(ty.params().len()).unwrap(), |
76 | 0 | u32::try_from(ty.results().len()).unwrap(), |
77 | 0 | )) |
78 | 0 | } |
79 | | |
80 | 0 | fn type_index_of_function(&self, func_idx: u32) -> Option<u32> { |
81 | 0 | self.resources.type_index_of_function(func_idx) |
82 | 0 | } |
83 | | |
84 | 0 | fn func_type_of_cont_type(&self, cont_ty: &crate::ContType) -> Option<&crate::FuncType> { |
85 | 0 | let id = cont_ty.0.as_core_type_id()?; |
86 | 0 | Some(self.resources.sub_type_at_id(id).unwrap_func()) |
87 | 0 | } |
88 | | |
89 | 0 | fn sub_type_of_ref_type(&self, rt: &crate::RefType) -> Option<&crate::SubType> { |
90 | 0 | let id = rt.type_index()?.as_core_type_id()?; |
91 | 0 | Some(self.resources.sub_type_at_id(id)) |
92 | 0 | } |
93 | | |
94 | 0 | fn control_stack_height(&self) -> u32 { |
95 | 0 | u32::try_from(self.validator.control_stack_height()).unwrap() |
96 | 0 | } |
97 | | |
98 | 0 | fn label_block(&self, depth: u32) -> Option<(crate::BlockType, crate::FrameKind)> { |
99 | 0 | self.validator.jump(depth) |
100 | 0 | } |
101 | | } |
102 | | |
103 | | /// External handle to the internal allocations used during function validation. |
104 | | /// |
105 | | /// This is created with either the `Default` implementation or with |
106 | | /// [`FuncValidator::into_allocations`]. It is then passed as an argument to |
107 | | /// [`FuncToValidate::into_validator`] to provide a means of reusing allocations |
108 | | /// between each function. |
109 | | #[derive(Default)] |
110 | | pub struct FuncValidatorAllocations(OperatorValidatorAllocations); |
111 | | |
112 | | impl<T: WasmModuleResources> FuncValidator<T> { |
113 | | /// Convenience function to validate an entire function's body. |
114 | | /// |
115 | | /// You may not end up using this in final implementations because you'll |
116 | | /// often want to interleave validation with parsing. |
117 | 226k | pub fn validate(&mut self, body: &FunctionBody<'_>) -> Result<()> { |
118 | 226k | let mut reader = body.get_binary_reader(); |
119 | 226k | self.read_locals(&mut reader)?; |
120 | | #[cfg(feature = "features")] |
121 | 226k | { |
122 | 226k | reader.set_features(self.validator.features); |
123 | 226k | } |
124 | 23.4M | while !reader.eof() { |
125 | | // In a `debug_check_try_op` build, verify that `rollback` successfully returns the |
126 | | // validator to its previous state after each (valid or invalid) operator. |
127 | | #[cfg(all(debug_check_try_op, feature = "try-op"))] |
128 | | { |
129 | | let snapshot = self.validator.clone(); |
130 | | let op = reader.peek_operator(&self.visitor(reader.original_position()))?; |
131 | | self.validator.begin_try_op(); |
132 | | let _ = self.op(reader.original_position(), &op); |
133 | | self.validator.rollback(); |
134 | | self.validator.pop_push_log.clear(); |
135 | | assert!(self.validator == snapshot); |
136 | | } |
137 | | |
138 | | // In a debug build, verify that the validator's pops and pushes to and from |
139 | | // the operand stack match the operator's arity. |
140 | | #[cfg(debug_assertions)] |
141 | | let (ops_before, arity) = { |
142 | | let op = reader.peek_operator(&self.visitor(reader.original_position()))?; |
143 | | let arity = op.operator_arity(&self.visitor(reader.original_position())); |
144 | | (reader.clone(), arity) |
145 | | }; |
146 | | |
147 | 23.1M | reader.visit_operator(&mut self.visitor(reader.original_position()))??; |
148 | | |
149 | | #[cfg(debug_assertions)] |
150 | | { |
151 | | let (params, results) = arity.ok_or(format_err!( |
152 | | reader.original_position(), |
153 | | "could not calculate operator arity" |
154 | | ))?; |
155 | | |
156 | | // Analyze the log to determine the actual, externally visible |
157 | | // pop/push count. This allows us to hide the fact that we might |
158 | | // push and then pop a temporary while validating an |
159 | | // instruction, which shouldn't be visible from the outside. |
160 | | let mut pop_count = 0; |
161 | | let mut push_count = 0; |
162 | | for op in self.validator.pop_push_log.drain(..) { |
163 | | match op { |
164 | | true => push_count += 1, |
165 | | false if push_count > 0 => push_count -= 1, |
166 | | false => pop_count += 1, |
167 | | } |
168 | | } |
169 | | |
170 | | if pop_count != params || push_count != results { |
171 | | panic!( |
172 | | "\ |
173 | | arity mismatch in validation |
174 | | operator: {:?} |
175 | | expected: {params} -> {results} |
176 | | got {pop_count} -> {push_count}", |
177 | | ops_before.peek_operator(&self.visitor(ops_before.original_position()))?, |
178 | | ); |
179 | | } |
180 | | } |
181 | | } |
182 | 226k | reader.finish_expression(&self.visitor(reader.original_position())) |
183 | 226k | } |
184 | | |
185 | | /// Reads the local definitions from the given `BinaryReader`, often sourced |
186 | | /// from a `FunctionBody`. |
187 | | /// |
188 | | /// This function will automatically advance the `BinaryReader` forward, |
189 | | /// leaving reading operators up to the caller afterwards. |
190 | 226k | pub fn read_locals(&mut self, reader: &mut BinaryReader<'_>) -> Result<()> { |
191 | 226k | for _ in 0..reader.read_var_u32()? { |
192 | 2.09M | let offset = reader.original_position(); |
193 | 2.09M | let cnt = reader.read()?; |
194 | 2.09M | let ty = reader.read()?; |
195 | 2.09M | self.define_locals(offset, cnt, ty)?; |
196 | | } |
197 | 226k | Ok(()) |
198 | 226k | } |
199 | | |
200 | | /// Defines locals into this validator. |
201 | | /// |
202 | | /// This should be used if the application is already reading local |
203 | | /// definitions and there's no need to re-parse the function again. |
204 | 2.09M | pub fn define_locals(&mut self, offset: usize, count: u32, ty: ValType) -> Result<()> { |
205 | 2.09M | self.validator |
206 | 2.09M | .define_locals(offset, count, ty, &self.resources) |
207 | 2.09M | } |
208 | | |
209 | | /// Validates the next operator in a function. |
210 | | /// |
211 | | /// This function is expected to be called once-per-operator in a |
212 | | /// WebAssembly function. Each operator's offset in the original binary and |
213 | | /// the operator itself are passed to this function to provide more useful |
214 | | /// error messages. On error, the validator may be left in an undefined |
215 | | /// state and should not be reused. |
216 | 0 | pub fn op(&mut self, offset: usize, operator: &Operator<'_>) -> Result<()> { |
217 | 0 | self.visitor(offset).visit_operator(operator) |
218 | 0 | } |
219 | | |
220 | | /// Validates the next operator in a function, rolling back the validator |
221 | | /// to its previous state if this is unsuccessful. The validator may be reused |
222 | | /// even after an error. |
223 | | #[cfg(feature = "try-op")] |
224 | | pub fn try_op(&mut self, offset: usize, operator: &Operator<'_>) -> Result<()> { |
225 | | self.validator.begin_try_op(); |
226 | | let res = self.op(offset, operator); |
227 | | if res.is_ok() { |
228 | | self.validator.commit(); |
229 | | } else { |
230 | | self.validator.rollback(); |
231 | | } |
232 | | res |
233 | | } |
234 | | |
235 | | /// Get the operator visitor for the next operator in the function. |
236 | | /// |
237 | | /// The returned visitor is intended to visit just one instruction at the `offset`. |
238 | | /// |
239 | | /// # Example |
240 | | /// |
241 | | /// ``` |
242 | | /// # use wasmparser::{WasmModuleResources, FuncValidator, FunctionBody, Result}; |
243 | | /// pub fn validate<R>(validator: &mut FuncValidator<R>, body: &FunctionBody<'_>) -> Result<()> |
244 | | /// where R: WasmModuleResources |
245 | | /// { |
246 | | /// let mut operator_reader = body.get_binary_reader_for_operators()?; |
247 | | /// while !operator_reader.eof() { |
248 | | /// let mut visitor = validator.visitor(operator_reader.original_position()); |
249 | | /// operator_reader.visit_operator(&mut visitor)??; |
250 | | /// } |
251 | | /// operator_reader.finish_expression(&validator.visitor(operator_reader.original_position())) |
252 | | /// } |
253 | | /// ``` |
254 | 23.4M | pub fn visitor<'this, 'a: 'this>( |
255 | 23.4M | &'this mut self, |
256 | 23.4M | offset: usize, |
257 | 23.4M | ) -> impl VisitOperator<'a, Output = Result<()>> + ModuleArity + FrameStack + 'this { |
258 | 23.4M | self.validator.with_resources(&self.resources, offset) |
259 | 23.4M | } |
260 | | |
261 | | /// Same as [`FuncValidator::visitor`] except that the returned type |
262 | | /// implements the [`VisitSimdOperator`](crate::VisitSimdOperator) trait as |
263 | | /// well. |
264 | | #[cfg(feature = "simd")] |
265 | 0 | pub fn simd_visitor<'this, 'a: 'this>( |
266 | 0 | &'this mut self, |
267 | 0 | offset: usize, |
268 | 0 | ) -> impl crate::VisitSimdOperator<'a, Output = Result<()>> + ModuleArity + 'this { |
269 | 0 | self.validator.with_resources_simd(&self.resources, offset) |
270 | 0 | } |
271 | | |
272 | | /// Returns the Wasm features enabled for this validator. |
273 | 0 | pub fn features(&self) -> &WasmFeatures { |
274 | 0 | &self.validator.features |
275 | 0 | } |
276 | | |
277 | | /// Returns the underlying module resources that this validator is using. |
278 | 0 | pub fn resources(&self) -> &T { |
279 | 0 | &self.resources |
280 | 0 | } |
281 | | |
282 | | /// The index of the function within the module's function index space that |
283 | | /// is being validated. |
284 | 0 | pub fn index(&self) -> u32 { |
285 | 0 | self.index |
286 | 0 | } |
287 | | |
288 | | /// Returns the number of defined local variables in the function. |
289 | 0 | pub fn len_locals(&self) -> u32 { |
290 | 0 | self.validator.locals.len_locals() |
291 | 0 | } |
292 | | |
293 | | /// Returns the type of the local variable at the given `index` if any. |
294 | 0 | pub fn get_local_type(&self, index: u32) -> Option<ValType> { |
295 | 0 | self.validator.locals.get(index) |
296 | 0 | } |
297 | | |
298 | | /// Get the current height of the operand stack. |
299 | | /// |
300 | | /// This returns the height of the whole operand stack for this function, |
301 | | /// not just for the current control frame. |
302 | 0 | pub fn operand_stack_height(&self) -> u32 { |
303 | 0 | self.validator.operand_stack_height() as u32 |
304 | 0 | } |
305 | | |
306 | | /// Returns the optional value type of the value operand at the given |
307 | | /// `depth` from the top of the operand stack. |
308 | | /// |
309 | | /// - Returns `None` if the `depth` is out of bounds. |
310 | | /// - Returns `Some(None)` if there is a value with unknown type |
311 | | /// at the given `depth`. |
312 | | /// |
313 | | /// # Note |
314 | | /// |
315 | | /// A `depth` of 0 will refer to the last operand on the stack. |
316 | 0 | pub fn get_operand_type(&self, depth: usize) -> Option<Option<ValType>> { |
317 | 0 | self.validator.peek_operand_at(depth) |
318 | 0 | } |
319 | | |
320 | | /// Returns the number of frames on the control flow stack. |
321 | | /// |
322 | | /// This returns the height of the whole control stack for this function, |
323 | | /// not just for the current control frame. |
324 | 0 | pub fn control_stack_height(&self) -> u32 { |
325 | 0 | self.validator.control_stack_height() as u32 |
326 | 0 | } |
327 | | |
328 | | /// Returns a shared reference to the control flow [`Frame`] of the |
329 | | /// control flow stack at the given `depth` if any. |
330 | | /// |
331 | | /// Returns `None` if the `depth` is out of bounds. |
332 | | /// |
333 | | /// # Note |
334 | | /// |
335 | | /// A `depth` of 0 will refer to the last frame on the stack. |
336 | 0 | pub fn get_control_frame(&self, depth: usize) -> Option<&Frame> { |
337 | 0 | self.validator.get_frame(depth) |
338 | 0 | } |
339 | | |
340 | | /// Consumes this validator and returns the underlying allocations that |
341 | | /// were used during the validation process. |
342 | | /// |
343 | | /// The returned value here can be paired with |
344 | | /// [`FuncToValidate::into_validator`] to reuse the allocations already |
345 | | /// created by this validator. |
346 | 226k | pub fn into_allocations(self) -> FuncValidatorAllocations { |
347 | 226k | FuncValidatorAllocations(self.validator.into_allocations()) |
348 | 226k | } |
349 | | } |
350 | | |
351 | | #[cfg(test)] |
352 | | mod tests { |
353 | | use super::*; |
354 | | use crate::types::CoreTypeId; |
355 | | use crate::{HeapType, Parser, RefType, Validator}; |
356 | | use alloc::vec::Vec; |
357 | | |
358 | | struct EmptyResources(crate::SubType); |
359 | | |
360 | | impl Default for EmptyResources { |
361 | | fn default() -> Self { |
362 | | EmptyResources(crate::SubType { |
363 | | supertype_idx: None, |
364 | | is_final: true, |
365 | | composite_type: crate::CompositeType { |
366 | | inner: crate::CompositeInnerType::Func(crate::FuncType::new([], [])), |
367 | | shared: false, |
368 | | descriptor_idx: None, |
369 | | describes_idx: None, |
370 | | }, |
371 | | }) |
372 | | } |
373 | | } |
374 | | |
375 | | impl WasmModuleResources for EmptyResources { |
376 | | fn table_at(&self, _at: u32) -> Option<crate::TableType> { |
377 | | todo!() |
378 | | } |
379 | | fn memory_at(&self, _at: u32) -> Option<crate::MemoryType> { |
380 | | todo!() |
381 | | } |
382 | | fn tag_at(&self, _at: u32) -> Option<&crate::FuncType> { |
383 | | todo!() |
384 | | } |
385 | | fn global_at(&self, _at: u32) -> Option<crate::GlobalType> { |
386 | | todo!() |
387 | | } |
388 | | fn sub_type_at(&self, _type_idx: u32) -> Option<&crate::SubType> { |
389 | | Some(&self.0) |
390 | | } |
391 | | fn sub_type_at_id(&self, _id: CoreTypeId) -> &crate::SubType { |
392 | | todo!() |
393 | | } |
394 | | fn type_id_of_function(&self, _at: u32) -> Option<CoreTypeId> { |
395 | | todo!() |
396 | | } |
397 | | fn type_index_of_function(&self, _at: u32) -> Option<u32> { |
398 | | todo!() |
399 | | } |
400 | | fn check_heap_type(&self, _t: &mut HeapType, _offset: usize) -> Result<()> { |
401 | | Ok(()) |
402 | | } |
403 | | fn top_type(&self, _heap_type: &HeapType) -> HeapType { |
404 | | todo!() |
405 | | } |
406 | | fn element_type_at(&self, _at: u32) -> Option<crate::RefType> { |
407 | | todo!() |
408 | | } |
409 | | fn is_subtype(&self, _t1: ValType, _t2: ValType) -> bool { |
410 | | todo!() |
411 | | } |
412 | | fn is_shared(&self, _ty: RefType) -> bool { |
413 | | todo!() |
414 | | } |
415 | | fn element_count(&self) -> u32 { |
416 | | todo!() |
417 | | } |
418 | | fn data_count(&self) -> Option<u32> { |
419 | | todo!() |
420 | | } |
421 | | fn is_function_referenced(&self, _idx: u32) -> bool { |
422 | | todo!() |
423 | | } |
424 | | fn has_function_exact_type(&self, _idx: u32) -> bool { |
425 | | todo!() |
426 | | } |
427 | | } |
428 | | |
429 | | #[test] |
430 | | fn operand_stack_height() { |
431 | | let mut v = FuncToValidate { |
432 | | index: 0, |
433 | | ty: 0, |
434 | | resources: EmptyResources::default(), |
435 | | features: Default::default(), |
436 | | } |
437 | | .into_validator(Default::default()); |
438 | | |
439 | | // Initially zero values on the stack. |
440 | | assert_eq!(v.operand_stack_height(), 0); |
441 | | |
442 | | // Pushing a constant value makes use have one value on the stack. |
443 | | assert!(v.op(0, &Operator::I32Const { value: 0 }).is_ok()); |
444 | | assert_eq!(v.operand_stack_height(), 1); |
445 | | |
446 | | // Entering a new control block does not affect the stack height. |
447 | | assert!( |
448 | | v.op( |
449 | | 1, |
450 | | &Operator::Block { |
451 | | blockty: crate::BlockType::Empty |
452 | | } |
453 | | ) |
454 | | .is_ok() |
455 | | ); |
456 | | assert_eq!(v.operand_stack_height(), 1); |
457 | | |
458 | | // Pushing another constant value makes use have two values on the stack. |
459 | | assert!(v.op(2, &Operator::I32Const { value: 99 }).is_ok()); |
460 | | assert_eq!(v.operand_stack_height(), 2); |
461 | | } |
462 | | |
463 | | fn assert_arity(wat: &str, expected: Vec<Vec<(u32, u32)>>) { |
464 | | let wasm = wat::parse_str(wat).unwrap(); |
465 | | assert!(Validator::new().validate_all(&wasm).is_ok()); |
466 | | |
467 | | let parser = Parser::new(0); |
468 | | let mut validator = Validator::new(); |
469 | | |
470 | | let mut actual = vec![]; |
471 | | |
472 | | for payload in parser.parse_all(&wasm) { |
473 | | let payload = payload.unwrap(); |
474 | | match payload { |
475 | | crate::Payload::CodeSectionEntry(body) => { |
476 | | let mut arity = vec![]; |
477 | | let mut func_validator = validator |
478 | | .code_section_entry(&body) |
479 | | .unwrap() |
480 | | .into_validator(FuncValidatorAllocations::default()); |
481 | | let ops = body.get_operators_reader().unwrap(); |
482 | | for op in ops.into_iter() { |
483 | | let op = op.unwrap(); |
484 | | arity.push( |
485 | | op.operator_arity(&func_validator) |
486 | | .expect("valid operators should have arity"), |
487 | | ); |
488 | | func_validator.op(usize::MAX, &op).expect("should be valid"); |
489 | | } |
490 | | actual.push(arity); |
491 | | } |
492 | | p => { |
493 | | validator.payload(&p).unwrap(); |
494 | | } |
495 | | } |
496 | | } |
497 | | |
498 | | assert_eq!(actual, expected); |
499 | | } |
500 | | |
501 | | #[test] |
502 | | fn arity_smoke_test() { |
503 | | let wasm = r#" |
504 | | (module |
505 | | (type $pair (struct (field i32) (field i32))) |
506 | | |
507 | | (func $add (param i32 i32) (result i32) |
508 | | local.get 0 |
509 | | local.get 1 |
510 | | i32.add |
511 | | ) |
512 | | |
513 | | (func $f (param i32 i32) (result (ref null $pair)) |
514 | | local.get 0 |
515 | | local.get 1 |
516 | | call $add |
517 | | if (result (ref null $pair)) |
518 | | local.get 0 |
519 | | local.get 1 |
520 | | struct.new $pair |
521 | | else |
522 | | unreachable |
523 | | i32.add |
524 | | unreachable |
525 | | end |
526 | | ) |
527 | | ) |
528 | | "#; |
529 | | |
530 | | assert_arity( |
531 | | wasm, |
532 | | vec![ |
533 | | // $add |
534 | | vec![ |
535 | | // local.get 0 |
536 | | (0, 1), |
537 | | // local.get 1 |
538 | | (0, 1), |
539 | | // i32.add |
540 | | (2, 1), |
541 | | // end |
542 | | (1, 1), |
543 | | ], |
544 | | // $f |
545 | | vec![ |
546 | | // local.get 0 |
547 | | (0, 1), |
548 | | // local.get 1 |
549 | | (0, 1), |
550 | | // call $add |
551 | | (2, 1), |
552 | | // if |
553 | | (1, 0), |
554 | | // local.get 0 |
555 | | (0, 1), |
556 | | // local.get 1 |
557 | | (0, 1), |
558 | | // struct.new $pair |
559 | | (2, 1), |
560 | | // else |
561 | | (1, 0), |
562 | | // unreachable, |
563 | | (0, 0), |
564 | | // i32.add |
565 | | (2, 1), |
566 | | // unreachable |
567 | | (0, 0), |
568 | | // end |
569 | | (1, 1), |
570 | | // implicit end |
571 | | (1, 1), |
572 | | ], |
573 | | ], |
574 | | ); |
575 | | } |
576 | | |
577 | | #[test] |
578 | | fn arity_if_no_else_same_params_and_results() { |
579 | | let wasm = r#" |
580 | | (module |
581 | | (func (export "f") (param i64 i32) (result i64) |
582 | | (local.get 0) |
583 | | (local.get 1) |
584 | | ;; If with no else. Same number of params and results. |
585 | | if (param i64) (result i64) |
586 | | drop |
587 | | i64.const -1 |
588 | | end |
589 | | ) |
590 | | ) |
591 | | "#; |
592 | | |
593 | | assert_arity( |
594 | | wasm, |
595 | | vec![vec![ |
596 | | // local.get 0 |
597 | | (0, 1), |
598 | | // local.get 1 |
599 | | (0, 1), |
600 | | // if |
601 | | (2, 1), |
602 | | // drop |
603 | | (1, 0), |
604 | | // i64.const -1 |
605 | | (0, 1), |
606 | | // end |
607 | | (1, 1), |
608 | | // implicit end |
609 | | (1, 1), |
610 | | ]], |
611 | | ); |
612 | | } |
613 | | |
614 | | #[test] |
615 | | fn arity_br_table() { |
616 | | let wasm = r#" |
617 | | (module |
618 | | (func (export "f") (result i32 i32) |
619 | | i32.const 0 |
620 | | i32.const 1 |
621 | | i32.const 2 |
622 | | br_table 0 0 |
623 | | ) |
624 | | ) |
625 | | "#; |
626 | | |
627 | | assert_arity( |
628 | | wasm, |
629 | | vec![vec![ |
630 | | // i32.const 0 |
631 | | (0, 1), |
632 | | // i32.const 1 |
633 | | (0, 1), |
634 | | // i32.const 2 |
635 | | (0, 1), |
636 | | // br_table |
637 | | (3, 0), |
638 | | // implicit end |
639 | | (2, 2), |
640 | | ]], |
641 | | ); |
642 | | } |
643 | | } |