Skip to main content

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}