wasm_interface/
validate.rs

1//! Validate a wasm module given a interface.
2//!
3//! This checks that all imports are specified in the interface and that their types
4//! are correct, as well as that all exports that the interface expects are exported
5//! by the module and that their types are correct.
6
7use crate::{Export, Import, Interface, WasmType};
8use std::collections::HashMap;
9use wasmparser::{ExternalKind, FuncType, GlobalType, ImportSectionEntryType};
10
11pub fn validate_wasm_and_report_errors(
12    wasm: &[u8],
13    interface: &Interface,
14) -> Result<(), WasmValidationError> {
15    use wasmparser::WasmDecoder;
16
17    let mut errors: Vec<String> = vec![];
18    let mut import_fns: HashMap<(String, String), u32> = HashMap::new();
19    let mut export_fns: HashMap<String, u32> = HashMap::new();
20    let mut export_globals: HashMap<String, u32> = HashMap::new();
21    let mut type_defs: Vec<FuncType> = vec![];
22    let mut global_types: Vec<GlobalType> = vec![];
23    let mut fn_sigs: Vec<u32> = vec![];
24
25    let mut parser = wasmparser::ValidatingParser::new(wasm, None);
26    loop {
27        let state = parser.read();
28        match state {
29            wasmparser::ParserState::EndWasm => break,
30            wasmparser::ParserState::Error(e) => {
31                return Err(WasmValidationError::InvalidWasm {
32                    error: format!("{}", e),
33                }
34                .into());
35            }
36            wasmparser::ParserState::ImportSectionEntry {
37                module,
38                field,
39                ref ty,
40            } => match ty {
41                ImportSectionEntryType::Function(idx) => {
42                    import_fns.insert(Import::format_key(module, field), *idx);
43                    fn_sigs.push(*idx);
44                }
45                ImportSectionEntryType::Global(GlobalType { content_type, .. }) => {
46                    let global_type =
47                        wasmparser_type_into_wasm_type(*content_type).map_err(|err| {
48                            WasmValidationError::UnsupportedType {
49                                error: format!(
50                                    "Invalid type found in import \"{}\" \"{}\": {}",
51                                    module, field, err
52                                ),
53                            }
54                        })?;
55                    if let Some(val) = interface.imports.get(&Import::format_key(module, field)) {
56                        if let Import::Global { var_type, .. } = val {
57                            if *var_type != global_type {
58                                errors.push(format!(
59                                    "Invalid type on Global \"{}\". Expected {} found {}",
60                                    field, var_type, global_type
61                                ));
62                            }
63                        } else {
64                            errors.push(format!(
65                                "Invalid import type. Expected Global, found {:?}",
66                                val
67                            ));
68                        }
69                    } else {
70                        errors.push(format!(
71                            "Global import \"{}\" not found in the specified interface",
72                            field
73                        ));
74                    }
75                }
76                _ => (),
77            },
78            wasmparser::ParserState::ExportSectionEntry {
79                field,
80                index,
81                ref kind,
82            } => match kind {
83                ExternalKind::Function => {
84                    export_fns.insert(Export::format_key(field), *index);
85                }
86                ExternalKind::Global => {
87                    export_globals.insert(Export::format_key(field), *index);
88                }
89                _ => (),
90            },
91            wasmparser::ParserState::BeginGlobalSectionEntry(gt) => {
92                global_types.push(gt.clone());
93            }
94            wasmparser::ParserState::TypeSectionEntry(ft) => {
95                type_defs.push(ft.clone());
96            }
97            wasmparser::ParserState::FunctionSectionEntry(n) => {
98                fn_sigs.push(*n);
99            }
100            _ => {}
101        }
102    }
103
104    validate_imports(&import_fns, &type_defs, interface, &mut errors);
105    validate_export_fns(&export_fns, &type_defs, &fn_sigs, interface, &mut errors);
106    validate_export_globals(&export_globals, &global_types, interface, &mut errors);
107
108    if errors.is_empty() {
109        Ok(())
110    } else {
111        Err(WasmValidationError::InterfaceViolated { errors: errors })
112    }
113}
114
115/// Validates the import functions, checking the name and type against the given
116/// `Interface`
117fn validate_imports(
118    import_fns: &HashMap<(String, String), u32>,
119    type_defs: &Vec<FuncType>,
120    interface: &Interface,
121    errors: &mut Vec<String>,
122) {
123    for (key, val) in import_fns.iter() {
124        if let Some(interface_def) = interface.imports.get(key) {
125            let type_sig = if let Some(v) = type_defs.get(*val as usize) {
126                v
127            } else {
128                errors.push(format!(
129                    "Use of undeclared function reference \"{}\" in import function \"{}\" \"{}\"",
130                    val, key.0, key.1
131                ));
132                continue;
133            };
134            if let Import::Func { params, result, .. } = interface_def {
135                debug_assert!(type_sig.form == wasmparser::Type::Func);
136                for (i, param) in type_sig
137                    .params
138                    .iter()
139                    .cloned()
140                    .map(wasmparser_type_into_wasm_type)
141                    .enumerate()
142                {
143                    match param {
144                        Ok(t) => {
145                            if params.get(i).is_none() {
146                                errors.push(format!("Found {} args but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
147                                continue;
148                            }
149                            if t != params[i] {
150                                errors.push(format!(
151                                    "Type mismatch in params in imported func \"{}\" \"{}\": argument {}, expected {} found {}",
152                                    &key.0, &key.1, i + 1, params[i], t
153                                ));
154                            }
155                        }
156                        Err(e) => errors.push(format!(
157                            "Invalid type in func \"{}\" \"{}\": {}",
158                            &key.0, &key.1, e
159                        )),
160                    }
161                }
162                for (i, ret) in type_sig
163                    .returns
164                    .iter()
165                    .cloned()
166                    .map(wasmparser_type_into_wasm_type)
167                    .enumerate()
168                {
169                    match ret {
170                        Ok(t) => {
171                            if result.get(i).is_none() {
172                                errors.push(format!("Found {} returns but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
173                                continue;
174                            }
175
176                            if t != result[i] {
177                                errors.push(format!(
178                                    "Type mismatch in returns in func \"{}\" \"{}\", return {}, expected {} found {}",
179                                    &key.0, &key.1, i + 1, params[i], t
180                                ));
181                            }
182                        }
183                        Err(e) => errors.push(format!(
184                            "Invalid type in func \"{}\" \"{}\": {}",
185                            &key.0, &key.1, e
186                        )),
187                    }
188                }
189            }
190        } else {
191            // we didn't find the import at all in the interface
192            // TODO: improve error messages by including type information
193            errors.push(format!("Missing import \"{}\" \"{}\"", key.0, key.1));
194        }
195    }
196}
197
198/// Validates the export functions, checking the name and type against the given
199/// `Interface`
200fn validate_export_fns(
201    export_fns: &HashMap<String, u32>,
202    type_defs: &Vec<FuncType>,
203    fn_sigs: &Vec<u32>,
204    interface: &Interface,
205    errors: &mut Vec<String>,
206) {
207    'export_loop: for (key, val) in export_fns.iter() {
208        if let Some(interface_def) = interface.exports.get(key) {
209            let type_sig = if let Some(type_idx) = fn_sigs.get(*val as usize) {
210                if let Some(v) = type_defs.get(*type_idx as usize) {
211                    v
212                } else {
213                    errors.push(format!(
214                        "Export \"{}\" refers to type \"{}\" but only {} types were found",
215                        &key,
216                        type_idx,
217                        fn_sigs.len()
218                    ));
219                    continue;
220                }
221            } else {
222                errors.push(format!(
223                    "Use of undeclared function reference \"{}\" in export \"{}\"",
224                    val, &key
225                ));
226                continue;
227            };
228            if let Export::Func { params, result, .. } = interface_def {
229                debug_assert!(type_sig.form == wasmparser::Type::Func);
230                for (i, param) in type_sig
231                    .params
232                    .iter()
233                    .cloned()
234                    .map(wasmparser_type_into_wasm_type)
235                    .enumerate()
236                {
237                    match param {
238                        Ok(t) => {
239                            if params.get(i).is_none() {
240                                errors.push(format!("Found {} args but the interface only expects {} for exported function \"{}\"", type_sig.params.len(), params.len(), &key));
241                                continue 'export_loop;
242                            }
243                            if t != params[i] {
244                                errors.push(format!(
245                                    "Type mismatch in params in exported func \"{}\": in argument {}, expected {} found {}",
246                                    &key, i + 1, params[i], t
247                                ));
248                            }
249                        }
250                        Err(e) => errors
251                            .push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
252                    }
253                }
254                for (i, ret) in type_sig
255                    .returns
256                    .iter()
257                    .cloned()
258                    .map(wasmparser_type_into_wasm_type)
259                    .enumerate()
260                {
261                    match ret {
262                        Ok(t) => {
263                            if result.get(i).is_none() {
264                                errors.push(format!("Found {} returns but the interface only expects {} for exported function \"{}\"", i, params.len(), &key));
265                                continue 'export_loop;
266                            }
267
268                            if t != result[i] {
269                                errors.push(format!(
270                                    "Type mismatch in returns in exported func \"{}\": in return {}, expected {} found {}",
271                                    &key, i + 1, result[i], t
272                                ));
273                            }
274                        }
275                        Err(e) => errors
276                            .push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
277                    }
278                }
279            }
280        }
281    }
282}
283
284/// Validates the export globals, checking the name and type against the given
285/// `Interface`
286fn validate_export_globals(
287    export_globals: &HashMap<String, u32>,
288    global_types: &Vec<GlobalType>,
289    interface: &Interface,
290    errors: &mut Vec<String>,
291) {
292    for (key, val) in export_globals.iter() {
293        if let Some(interface_def) = interface.exports.get(key) {
294            if let Export::Global { var_type, .. } = interface_def {
295                if global_types.get(*val as usize).is_none() {
296                    errors.push(format!(
297                        "Invalid wasm, expected {} global types, found {}",
298                        val,
299                        global_types.len()
300                    ));
301                }
302                match wasmparser_type_into_wasm_type(global_types[*val as usize].content_type) {
303                    Ok(t) => {
304                        if *var_type != t {
305                            errors.push(format!(
306                                "Type mismatch in global export {}: expected {} found {}",
307                                &key, var_type, t
308                            ));
309                        }
310                    }
311                    Err(e) => errors.push(format!("In global export {}: {}", &key, e)),
312                }
313            }
314        }
315    }
316}
317
318/// Converts Wasmparser's type enum into wasm-interface's type enum
319/// wasmparser's enum contains things which are invalid in many situations
320///
321/// Additionally wasmerparser containers more advanced types like references that
322/// wasm-interface does not yet support
323fn wasmparser_type_into_wasm_type(ty: wasmparser::Type) -> Result<WasmType, String> {
324    use wasmparser::Type;
325    Ok(match ty {
326        Type::I32 => WasmType::I32,
327        Type::I64 => WasmType::I64,
328        Type::F32 => WasmType::F32,
329        Type::F64 => WasmType::F64,
330        e => {
331            return Err(format!("Invalid type found: {:?}", e));
332        }
333    })
334}
335
336#[cfg(test)]
337mod validation_tests {
338    use super::*;
339    use crate::parser;
340
341    #[test]
342    fn global_imports() {
343        const WAT: &str = r#"(module
344(type $t0 (func (param i32 i64)))
345(global $length (import "env" "length") i32)
346(import "env" "do_panic" (func $do_panic (type $t0)))
347)"#;
348        let wasm = wabt::wat2wasm(WAT).unwrap();
349
350        let interface_src = r#"
351(interface 
352(func (import "env" "do_panic") (param i32 i64))
353(global (import "env" "length") (type i32)))"#;
354        let interface = parser::parse_interface(interface_src).unwrap();
355
356        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
357
358        assert!(result.is_ok());
359
360        // Now set the global import type to mismatch the wasm
361        let interface_src = r#"
362(interface 
363(func (import "env" "do_panic") (param i32 i64))
364(global (import "env" "length") (type i64)))"#;
365        let interface = parser::parse_interface(interface_src).unwrap();
366
367        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
368
369        assert!(
370            result.is_err(),
371            "global import type mismatch causes an error"
372        );
373
374        // Now set the function import type to mismatch the wasm
375        let interface_src = r#"
376(interface 
377(func (import "env" "do_panic") (param i64))
378(global (import "env" "length") (type i32)))"#;
379        let interface = parser::parse_interface(interface_src).unwrap();
380
381        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
382
383        assert!(
384            result.is_err(),
385            "function import type mismatch causes an error"
386        );
387
388        // Now try with a module that has an import that the interface doesn't have
389        let interface_src = r#"
390(interface
391(func (import "env" "do_panic") (param i64))
392(global (import "env" "length_plus_plus") (type i32)))"#;
393        let interface = parser::parse_interface(interface_src).unwrap();
394
395        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
396
397        assert!(
398            result.is_err(),
399            "all imports must be covered by the interface"
400        );
401    }
402
403    #[test]
404    fn global_exports() {
405        const WAT: &str = r#"(module
406(func (export "as-set_local-first") (param i32) (result i32)
407  (nop) (i32.const 2) (set_local 0) (get_local 0))
408(global (export "num_tries") i64 (i64.const 0))
409)"#;
410        let wasm = wabt::wat2wasm(WAT).unwrap();
411
412        let interface_src = r#"
413(interface 
414(func (export "as-set_local-first") (param i32) (result i32))
415(global (export "num_tries") (type i64)))"#;
416        let interface = parser::parse_interface(interface_src).unwrap();
417
418        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
419
420        assert!(result.is_ok());
421
422        // Now set the global export type to mismatch the wasm
423        let interface_src = r#"
424(interface
425(func (export "as-set_local-first") (param i32) (result i32))
426(global (export "num_tries") (type f32)))"#;
427        let interface = parser::parse_interface(interface_src).unwrap();
428
429        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
430
431        assert!(
432            result.is_err(),
433            "global export type mismatch causes an error"
434        );
435
436        // Now set the function export type to mismatch the wasm
437        let interface_src = r#"
438(interface
439(func (export "as-set_local-first") (param i64) (result i64))
440(global (export "num_tries") (type i64)))"#;
441        let interface = parser::parse_interface(interface_src).unwrap();
442
443        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
444
445        assert!(
446            result.is_err(),
447            "function export type mismatch causes an error"
448        );
449
450        // Now try a interface that requires an export that the module doesn't have
451        let interface_src = r#"
452(interface
453(func (export "as-set_local-first") (param i64) (result i64))
454(global (export "numb_trees") (type i64)))"#;
455        let interface = parser::parse_interface(interface_src).unwrap();
456
457        let result = validate_wasm_and_report_errors(&wasm[..], &interface);
458
459        assert!(result.is_err(), "missing a required export is an error");
460    }
461}
462
463#[derive(Debug)]
464pub enum WasmValidationError {
465    InvalidWasm { error: String },
466    InterfaceViolated { errors: Vec<String> },
467    UnsupportedType { error: String },
468}