wasmparser/validator/func.rs
1use super::operators::{Frame, OperatorValidator, OperatorValidatorAllocations};
2use crate::{BinaryReader, Result, ValType, VisitOperator};
3use 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)]
13pub 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
25impl<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 pub fn into_validator(self, allocs: FuncValidatorAllocations) -> FuncValidator<T> {
40 let FuncToValidate {
41 resources,
42 index,
43 ty,
44 features,
45 } = self;
46 let validator =
47 OperatorValidator::new_func(ty, 0, &features, &resources, allocs.0).unwrap();
48 FuncValidator {
49 validator,
50 resources,
51 index,
52 }
53 }
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)]
61pub struct FuncValidator<T> {
62 validator: OperatorValidator,
63 resources: T,
64 index: u32,
65}
66
67impl<T: WasmModuleResources> ModuleArity for FuncValidator<T> {
68 fn sub_type_at(&self, type_idx: u32) -> Option<&crate::SubType> {
69 self.resources.sub_type_at(type_idx)
70 }
71
72 fn tag_type_arity(&self, at: u32) -> Option<(u32, u32)> {
73 let ty = self.resources.tag_at(at)?;
74 Some((
75 u32::try_from(ty.params().len()).unwrap(),
76 u32::try_from(ty.results().len()).unwrap(),
77 ))
78 }
79
80 fn type_index_of_function(&self, func_idx: u32) -> Option<u32> {
81 self.resources.type_index_of_function(func_idx)
82 }
83
84 fn func_type_of_cont_type(&self, cont_ty: &crate::ContType) -> Option<&crate::FuncType> {
85 let id = cont_ty.0.as_core_type_id()?;
86 Some(self.resources.sub_type_at_id(id).unwrap_func())
87 }
88
89 fn sub_type_of_ref_type(&self, rt: &crate::RefType) -> Option<&crate::SubType> {
90 let id = rt.type_index()?.as_core_type_id()?;
91 Some(self.resources.sub_type_at_id(id))
92 }
93
94 fn control_stack_height(&self) -> u32 {
95 u32::try_from(self.validator.control_stack_height()).unwrap()
96 }
97
98 fn label_block(&self, depth: u32) -> Option<(crate::BlockType, crate::FrameKind)> {
99 self.validator.jump(depth)
100 }
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)]
110pub struct FuncValidatorAllocations(OperatorValidatorAllocations);
111
112impl<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 pub fn validate(&mut self, body: &FunctionBody<'_>) -> Result<()> {
118 let mut reader = body.get_binary_reader();
119 self.read_locals(&mut reader)?;
120 #[cfg(feature = "features")]
121 {
122 reader.set_features(self.validator.features);
123 }
124 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 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 "\
173arity 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 reader.finish_expression(&self.visitor(reader.original_position()))
183 }
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 pub fn read_locals(&mut self, reader: &mut BinaryReader<'_>) -> Result<()> {
191 for _ in 0..reader.read_var_u32()? {
192 let offset = reader.original_position();
193 let cnt = reader.read()?;
194 let ty = reader.read()?;
195 self.define_locals(offset, cnt, ty)?;
196 }
197 Ok(())
198 }
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 pub fn define_locals(&mut self, offset: usize, count: u32, ty: ValType) -> Result<()> {
205 self.validator
206 .define_locals(offset, count, ty, &self.resources)
207 }
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 pub fn op(&mut self, offset: usize, operator: &Operator<'_>) -> Result<()> {
217 self.visitor(offset).visit_operator(operator)
218 }
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 pub fn visitor<'this, 'a: 'this>(
255 &'this mut self,
256 offset: usize,
257 ) -> impl VisitOperator<'a, Output = Result<()>> + ModuleArity + FrameStack + 'this {
258 self.validator.with_resources(&self.resources, offset)
259 }
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 pub fn simd_visitor<'this, 'a: 'this>(
266 &'this mut self,
267 offset: usize,
268 ) -> impl crate::VisitSimdOperator<'a, Output = Result<()>> + ModuleArity + 'this {
269 self.validator.with_resources_simd(&self.resources, offset)
270 }
271
272 /// Returns the Wasm features enabled for this validator.
273 pub fn features(&self) -> &WasmFeatures {
274 &self.validator.features
275 }
276
277 /// Returns the underlying module resources that this validator is using.
278 pub fn resources(&self) -> &T {
279 &self.resources
280 }
281
282 /// The index of the function within the module's function index space that
283 /// is being validated.
284 pub fn index(&self) -> u32 {
285 self.index
286 }
287
288 /// Returns the number of defined local variables in the function.
289 pub fn len_locals(&self) -> u32 {
290 self.validator.locals.len_locals()
291 }
292
293 /// Returns the type of the local variable at the given `index` if any.
294 pub fn get_local_type(&self, index: u32) -> Option<ValType> {
295 self.validator.locals.get(index)
296 }
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 pub fn operand_stack_height(&self) -> u32 {
303 self.validator.operand_stack_height() as u32
304 }
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 pub fn get_operand_type(&self, depth: usize) -> Option<Option<ValType>> {
317 self.validator.peek_operand_at(depth)
318 }
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 pub fn control_stack_height(&self) -> u32 {
325 self.validator.control_stack_height() as u32
326 }
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 pub fn get_control_frame(&self, depth: usize) -> Option<&Frame> {
337 self.validator.get_frame(depth)
338 }
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 pub fn into_allocations(self) -> FuncValidatorAllocations {
347 FuncValidatorAllocations(self.validator.into_allocations())
348 }
349}
350
351#[cfg(test)]
352mod 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}