cosmwasm_vm/
parsed_wasm.rs

1use std::{fmt, mem, str};
2
3use wasmer::wasmparser::{
4    BinaryReaderError, CompositeInnerType, Export, FuncToValidate, FunctionBody, Import,
5    MemoryType, Parser, Payload, TableType, ValidPayload, Validator, ValidatorResources,
6    WasmFeatures,
7};
8
9use crate::{VmError, VmResult};
10
11/// Opaque wrapper type implementing `Debug`
12///
13/// The purpose of this type is to wrap types that do not implement `Debug` themselves.
14/// For example, you have a large struct and derive `Debug` on it but one member does not implement the trait, that's where this type comes in.
15///
16/// Instead of printing a full debug representation of the underlying data, it simply prints something akin to this:
17///
18/// ```ignore
19/// WrappedType { ... }
20/// ```
21#[derive(Default)]
22pub struct OpaqueDebug<T>(pub T);
23
24impl<T> fmt::Debug for OpaqueDebug<T> {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        f.debug_struct(std::any::type_name::<T>())
27            .finish_non_exhaustive()
28    }
29}
30
31#[derive(Debug)]
32pub enum FunctionValidator<'a> {
33    Pending(OpaqueDebug<Vec<(FuncToValidate<ValidatorResources>, FunctionBody<'a>)>>),
34    Success,
35    Error(BinaryReaderError),
36}
37
38impl<'a> FunctionValidator<'a> {
39    fn push(&mut self, item: (FuncToValidate<ValidatorResources>, FunctionBody<'a>)) {
40        let Self::Pending(OpaqueDebug(ref mut funcs)) = self else {
41            panic!("attempted to push function into non-pending validator");
42        };
43
44        funcs.push(item);
45    }
46}
47
48/// A parsed and validated wasm module.
49/// It keeps track of the parts that are important for our static analysis and compatibility checks.
50#[derive(Debug)]
51pub struct ParsedWasm<'a> {
52    pub version: u16,
53    pub exports: Vec<Export<'a>>,
54    pub imports: Vec<Import<'a>>,
55    pub tables: Vec<TableType>,
56    pub memories: Vec<MemoryType>,
57    pub function_count: usize,
58    pub type_count: u32,
59    /// How many parameters a type has.
60    /// The index is the type id
61    pub type_params: Vec<usize>,
62    /// How many parameters the function with the most parameters has
63    pub max_func_params: usize,
64    /// How many results the function with the most results has
65    pub max_func_results: usize,
66    /// How many function parameters are used in the module
67    pub total_func_params: usize,
68    /// Collections of functions that are potentially pending validation
69    pub func_validator: FunctionValidator<'a>,
70    /// Contract migrate version as defined in a custom section
71    pub contract_migrate_version: Option<u64>,
72}
73
74impl<'a> ParsedWasm<'a> {
75    pub fn parse(wasm: &'a [u8]) -> VmResult<Self> {
76        let features = WasmFeatures::MUTABLE_GLOBAL
77            | WasmFeatures::SATURATING_FLOAT_TO_INT
78            | WasmFeatures::SIGN_EXTENSION
79            | WasmFeatures::MULTI_VALUE
80            | WasmFeatures::FLOATS
81            | WasmFeatures::REFERENCE_TYPES;
82
83        let mut validator = Validator::new_with_features(features);
84
85        let mut this = Self {
86            version: 0,
87            exports: vec![],
88            imports: vec![],
89            tables: vec![],
90            memories: vec![],
91            function_count: 0,
92            type_count: 0,
93            type_params: Vec::new(),
94            max_func_params: 0,
95            max_func_results: 0,
96            total_func_params: 0,
97            func_validator: FunctionValidator::Pending(OpaqueDebug::default()),
98            contract_migrate_version: None,
99        };
100
101        for p in Parser::new(0).parse_all(wasm) {
102            let p = p?;
103            // validate the payload
104            if let ValidPayload::Func(fv, body) = validator.payload(&p)? {
105                // also validate function bodies
106                this.func_validator.push((fv, body));
107                this.function_count += 1;
108            }
109
110            match p {
111                Payload::TypeSection(t) => {
112                    this.type_count = 0;
113                    // t.count() is a lower bound
114                    this.type_params = Vec::with_capacity(t.count() as usize);
115                    for group in t.into_iter() {
116                        let types = group?.into_types();
117                        // update count
118                        this.type_count += types.len() as u32;
119
120                        for ty in types {
121                            match ty.composite_type.inner {
122                                CompositeInnerType::Func(ft) => {
123                                    this.type_params.push(ft.params().len());
124
125                                    this.max_func_params =
126                                        core::cmp::max(ft.params().len(), this.max_func_params);
127                                    this.max_func_results =
128                                        core::cmp::max(ft.results().len(), this.max_func_results);
129                                }
130                                CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => {
131                                    // ignoring these for now, as they are only available with the GC
132                                    // proposal and we explicitly disabled that above
133                                }
134                            }
135                        }
136                    }
137                }
138                Payload::FunctionSection(section) => {
139                    // In valid Wasm, the function section always has to come after the type section
140                    // (see https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA),
141                    // so we can assume that the type_params map is already filled at this point
142
143                    for a in section {
144                        let type_index = a? as usize;
145                        this.total_func_params +=
146                            this.type_params.get(type_index).ok_or_else(|| {
147                                // this will also be thrown if the wasm section order is invalid
148                                VmError::static_validation_err(
149                                    "Wasm bytecode error: function uses unknown type index",
150                                )
151                            })?
152                    }
153                }
154                Payload::Version { num, .. } => this.version = num,
155                Payload::ImportSection(i) => {
156                    this.imports = i.into_iter().collect::<Result<Vec<_>, _>>()?;
157                }
158                Payload::TableSection(t) => {
159                    this.tables = t
160                        .into_iter()
161                        .map(|r| r.map(|t| t.ty))
162                        .collect::<Result<Vec<_>, _>>()?;
163                }
164                Payload::MemorySection(m) => {
165                    this.memories = m.into_iter().collect::<Result<Vec<_>, _>>()?;
166                }
167                Payload::ExportSection(e) => {
168                    this.exports = e.into_iter().collect::<Result<Vec<_>, _>>()?;
169                }
170                Payload::CustomSection(reader) if reader.name() == "cw_migrate_version" => {
171                    // This is supposed to be valid UTF-8
172                    let raw_version = str::from_utf8(reader.data())
173                        .map_err(|err| VmError::static_validation_err(err.to_string()))?;
174
175                    this.contract_migrate_version = Some(
176                        raw_version
177                            .parse::<u64>()
178                            .map_err(|err| VmError::static_validation_err(err.to_string()))?,
179                    );
180                }
181                _ => {} // ignore everything else
182            }
183        }
184
185        Ok(this)
186    }
187
188    /// Perform the expensive operation of validating each function body
189    ///
190    /// Note: This function caches the output of this function into the field `func_validator` so repeated invocations are cheap.
191    pub fn validate_funcs(&mut self) -> VmResult<()> {
192        match self.func_validator {
193            FunctionValidator::Pending(OpaqueDebug(ref mut funcs)) => {
194                let result = (|| {
195                    let mut allocations = <_>::default();
196                    for (func, body) in mem::take(funcs) {
197                        let mut validator = func.into_validator(allocations);
198                        validator.validate(&body)?;
199                        allocations = validator.into_allocations();
200                    }
201                    Ok(())
202                })();
203
204                self.func_validator = match result {
205                    Ok(()) => FunctionValidator::Success,
206                    Err(err) => FunctionValidator::Error(err),
207                };
208
209                self.validate_funcs()
210            }
211            FunctionValidator::Success => Ok(()),
212            FunctionValidator::Error(ref err) => Err(err.clone().into()),
213        }
214    }
215}
216
217#[cfg(test)]
218mod test {
219    use super::ParsedWasm;
220
221    #[test]
222    fn read_migrate_version() {
223        let wasm_data =
224            wat::parse_str(r#"( module ( @custom "cw_migrate_version" "42" ) )"#).unwrap();
225        let parsed = ParsedWasm::parse(&wasm_data).unwrap();
226
227        assert_eq!(parsed.contract_migrate_version, Some(42));
228    }
229
230    #[test]
231    fn read_migrate_version_fails() {
232        let wasm_data =
233            wat::parse_str(r#"( module ( @custom "cw_migrate_version" "not a number" ) )"#)
234                .unwrap();
235        assert!(ParsedWasm::parse(&wasm_data).is_err());
236    }
237
238    #[test]
239    fn parsed_wasm_counts_functions_correctly() {
240        let wasm = wat::parse_str(r#"(module)"#).unwrap();
241        let module = ParsedWasm::parse(&wasm).unwrap();
242        assert_eq!(module.function_count, 0);
243
244        let wasm = wat::parse_str(
245            r#"(module
246            (type (func))
247            (func (type 0) nop)
248            (func (type 0) nop)
249            (export "foo" (func 0))
250            (export "bar" (func 0))
251        )"#,
252        )
253        .unwrap();
254        let module = ParsedWasm::parse(&wasm).unwrap();
255        assert_eq!(module.function_count, 2);
256    }
257
258    #[test]
259    fn parsed_wasm_counts_func_io_correctly() {
260        let wasm = wat::parse_str(r#"(module)"#).unwrap();
261        let module = ParsedWasm::parse(&wasm).unwrap();
262        assert_eq!(module.max_func_params, 0);
263        assert_eq!(module.max_func_results, 0);
264
265        let wasm = wat::parse_str(
266            r#"(module
267            (type (func (param i32 i32 i32) (result i32)))
268            (type (func (param i32) (result i32 i32)))
269            (func (type 1) i32.const 42 i32.const 42)
270            (func (type 0) i32.const 42)
271        )"#,
272        )
273        .unwrap();
274        let module = ParsedWasm::parse(&wasm).unwrap();
275        assert_eq!(module.max_func_params, 3);
276        assert_eq!(module.max_func_results, 2);
277    }
278}