twasmi_validation/
util.rs

1use crate::Error;
2use alloc::string::String;
3use tetsy_wasm::elements::{Local, ValueType};
4
5#[cfg(test)]
6use assert_matches::assert_matches;
7
8/// Locals are the concatenation of a slice of function parameters
9/// with function declared local variables.
10///
11/// Local variables are given in the form of groups represented by pairs
12/// of a value_type and a count.
13#[derive(Debug)]
14pub struct Locals<'a> {
15    params: &'a [ValueType],
16    local_groups: &'a [Local],
17    count: u32,
18}
19
20impl<'a> Locals<'a> {
21    /// Create a new wrapper around declared variables and parameters.
22    pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Result<Locals<'a>, Error> {
23        let mut acc = params.len() as u32;
24        for locals_group in local_groups {
25            acc = acc
26                .checked_add(locals_group.count())
27                .ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
28        }
29
30        Ok(Locals {
31            params,
32            local_groups,
33            count: acc,
34        })
35    }
36
37    /// Returns parameter count.
38    pub fn param_count(&self) -> u32 {
39        self.params.len() as u32
40    }
41
42    /// Returns total count of all declared locals and paramaterers.
43    pub fn count(&self) -> u32 {
44        self.count
45    }
46
47    /// Returns the type of a local variable (either a declared local or a param).
48    ///
49    /// Returns `Err` in the case of overflow or when idx falls out of range.
50    pub fn type_of_local(&self, idx: u32) -> Result<ValueType, Error> {
51        if let Some(param) = self.params.get(idx as usize) {
52            return Ok(*param);
53        }
54
55        // If an index doesn't point to a param, then we have to look into local declarations.
56        let mut start_idx = self.param_count();
57        for locals_group in self.local_groups {
58            let end_idx = start_idx
59                .checked_add(locals_group.count())
60                .ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
61
62            if idx >= start_idx && idx < end_idx {
63                return Ok(locals_group.value_type());
64            }
65
66            start_idx = end_idx;
67        }
68
69        // We didn't find anything, that's an error.
70        // At this moment `start_idx` should hold the count of all locals
71        // (since it's either set to the `end_idx` or equal to `params.len()`)
72        let total_count = start_idx;
73
74        Err(Error(format!(
75            "Trying to access local with index {} when there are only {} locals",
76            idx, total_count
77        )))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn locals_it_works() {
87        let params = vec![ValueType::I32, ValueType::I64];
88        let local_groups = vec![Local::new(2, ValueType::F32), Local::new(2, ValueType::F64)];
89        let locals = Locals::new(&params, &local_groups).unwrap();
90
91        assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
92        assert_matches!(locals.type_of_local(1), Ok(ValueType::I64));
93        assert_matches!(locals.type_of_local(2), Ok(ValueType::F32));
94        assert_matches!(locals.type_of_local(3), Ok(ValueType::F32));
95        assert_matches!(locals.type_of_local(4), Ok(ValueType::F64));
96        assert_matches!(locals.type_of_local(5), Ok(ValueType::F64));
97        assert_matches!(locals.type_of_local(6), Err(_));
98    }
99
100    #[test]
101    fn locals_no_declared_locals() {
102        let params = vec![ValueType::I32];
103        let locals = Locals::new(&params, &[]).unwrap();
104
105        assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
106        assert_matches!(locals.type_of_local(1), Err(_));
107    }
108
109    #[test]
110    fn locals_no_params() {
111        let local_groups = vec![Local::new(2, ValueType::I32), Local::new(3, ValueType::I64)];
112        let locals = Locals::new(&[], &local_groups).unwrap();
113
114        assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
115        assert_matches!(locals.type_of_local(1), Ok(ValueType::I32));
116        assert_matches!(locals.type_of_local(2), Ok(ValueType::I64));
117        assert_matches!(locals.type_of_local(3), Ok(ValueType::I64));
118        assert_matches!(locals.type_of_local(4), Ok(ValueType::I64));
119        assert_matches!(locals.type_of_local(5), Err(_));
120    }
121
122    #[test]
123    fn locals_u32_overflow() {
124        let local_groups = vec![
125            Local::new(u32::max_value(), ValueType::I32),
126            Local::new(1, ValueType::I64),
127        ];
128        assert_matches!(Locals::new(&[], &local_groups), Err(_));
129    }
130}