Coverage Report

Created: 2026-06-07 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}