1use 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
115fn 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 errors.push(format!("Missing import \"{}\" \"{}\"", key.0, key.1));
194 }
195 }
196}
197
198fn 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
284fn 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
318fn 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 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 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 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 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 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 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}