1use crate::{Export, Import, Interface, WasmType};
8use std::collections::HashMap;
9use wasmparser::{CompositeType, ExternalKind, FuncType, GlobalType, Payload, TypeRef};
10
11pub fn validate_wasm_and_report_errors(
12 wasm: &[u8],
13 interface: &Interface,
14) -> Result<(), WasmValidationError> {
15 let mut errors: Vec<String> = vec![];
16 let mut import_fns: HashMap<(String, String), u32> = HashMap::new();
17 let mut export_fns: HashMap<String, u32> = HashMap::new();
18 let mut export_globals: HashMap<String, u32> = HashMap::new();
19 let mut type_defs: Vec<FuncType> = vec![];
20 let mut global_types: Vec<GlobalType> = vec![];
21 let mut fn_sigs: Vec<u32> = vec![];
22
23 let mut val = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
24 threads: true,
25 reference_types: true,
26 simd: true,
27 bulk_memory: true,
28 multi_value: true,
29 ..Default::default()
30 });
31
32 val.validate_all(wasm)
33 .map_err(|e| WasmValidationError::InvalidWasm {
34 error: format!("{}", e),
35 })?;
36
37 let parser = wasmparser::Parser::new(0).parse_all(wasm);
38
39 for res in parser {
40 let payload = res.map_err(|e| WasmValidationError::InvalidWasm {
41 error: format!("{}", e),
42 })?;
43 match payload {
44 Payload::End(_) => break,
45 Payload::ImportSection(reader) => {
46 for item in reader.into_iter_with_offsets() {
47 let (_offset, import) = item.map_err(|e| WasmValidationError::InvalidWasm {
48 error: format!("{}", e),
49 })?;
50
51 match import.ty {
52 TypeRef::Func(idx) => {
53 import_fns.insert(Import::format_key(import.module, import.name), idx);
54 fn_sigs.push(idx);
55 }
56 TypeRef::Global(GlobalType { content_type, .. }) => {
57 let global_type = wasmparser_type_into_wasm_type(content_type)
58 .map_err(|err| WasmValidationError::UnsupportedType {
59 error: format!(
60 "Invalid type found in import \"{}\" \"{}\": {}",
61 import.module, import.name, err
62 ),
63 })?;
64 if let Some(val) = interface
65 .imports
66 .get(&Import::format_key(import.module, import.name))
67 {
68 if let Import::Global { var_type, .. } = val {
69 if *var_type != global_type {
70 errors.push(format!(
71 "Invalid type on Global \"{}\". Expected {} found {}",
72 import.name, var_type, global_type
73 ));
74 }
75 } else {
76 errors.push(format!(
77 "Invalid import type. Expected Global, found {:?}",
78 val
79 ));
80 }
81 } else {
82 errors.push(format!(
83 "Global import \"{}\" not found in the specified interface",
84 import.name
85 ));
86 }
87 }
88 _ => (),
89 }
90 }
91 }
92 Payload::ExportSection(reader) => {
93 for res in reader.into_iter() {
94 let export = res.map_err(|e| WasmValidationError::InvalidWasm {
95 error: format!("{}", e),
96 })?;
97
98 match export.kind {
99 ExternalKind::Func => {
100 export_fns.insert(Export::format_key(export.name), export.index);
101 }
102 ExternalKind::Global => {
103 export_globals.insert(Export::format_key(export.name), export.index);
104 }
105 _ => (),
106 }
107 }
108 }
109 Payload::GlobalSection(reader) => {
110 for res in reader.into_iter() {
111 let global = res.map_err(|e| WasmValidationError::InvalidWasm {
112 error: format!("{}", e),
113 })?;
114
115 global_types.push(global.ty);
116 }
117 }
118 Payload::TypeSection(reader) => {
119 for res in reader.into_iter() {
120 let group = res.map_err(|e| WasmValidationError::InvalidWasm {
121 error: format!("{}", e),
122 })?;
123
124 for ty in group.into_types() {
125 if let CompositeType::Func(ft) = ty.composite_type {
126 type_defs.push(ft);
127 }
128 }
129 }
130 }
131 Payload::FunctionSection(reader) => {
132 for res in reader.into_iter() {
133 let func = res.map_err(|e| WasmValidationError::InvalidWasm {
134 error: format!("{}", e),
135 })?;
136
137 fn_sigs.push(func);
138 }
139 }
140 _ => {}
141 }
142 }
143
144 validate_imports(&import_fns, &type_defs, interface, &mut errors);
145 validate_export_fns(&export_fns, &type_defs, &fn_sigs, interface, &mut errors);
146 validate_export_globals(&export_globals, &global_types, interface, &mut errors);
147
148 if errors.is_empty() {
149 Ok(())
150 } else {
151 Err(WasmValidationError::InterfaceViolated { errors })
152 }
153}
154
155fn validate_imports(
158 import_fns: &HashMap<(String, String), u32>,
159 type_defs: &[FuncType],
160 interface: &Interface,
161 errors: &mut Vec<String>,
162) {
163 for (key, val) in import_fns.iter() {
164 if let Some(interface_def) = interface.imports.get(key) {
165 let type_sig = if let Some(v) = type_defs.get(*val as usize) {
166 v
167 } else {
168 errors.push(format!(
169 "Use of undeclared function reference \"{}\" in import function \"{}\" \"{}\"",
170 val, key.0, key.1
171 ));
172 continue;
173 };
174 if let Import::Func { params, result, .. } = interface_def {
175 for (i, param) in type_sig
176 .params()
177 .iter()
178 .cloned()
179 .map(wasmparser_type_into_wasm_type)
180 .enumerate()
181 {
182 match param {
183 Ok(t) => {
184 if params.get(i).is_none() {
185 errors.push(format!("Found {} args but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
186 continue;
187 }
188 if t != params[i] {
189 errors.push(format!(
190 "Type mismatch in params in imported func \"{}\" \"{}\": argument {}, expected {} found {}",
191 &key.0, &key.1, i + 1, params[i], t
192 ));
193 }
194 }
195 Err(e) => errors.push(format!(
196 "Invalid type in func \"{}\" \"{}\": {}",
197 &key.0, &key.1, e
198 )),
199 }
200 }
201 for (i, ret) in type_sig
202 .results()
203 .iter()
204 .cloned()
205 .map(wasmparser_type_into_wasm_type)
206 .enumerate()
207 {
208 match ret {
209 Ok(t) => {
210 if result.get(i).is_none() {
211 errors.push(format!("Found {} returns but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
212 continue;
213 }
214
215 if t != result[i] {
216 errors.push(format!(
217 "Type mismatch in returns in func \"{}\" \"{}\", return {}, expected {} found {}",
218 &key.0, &key.1, i + 1, params[i], t
219 ));
220 }
221 }
222 Err(e) => errors.push(format!(
223 "Invalid type in func \"{}\" \"{}\": {}",
224 &key.0, &key.1, e
225 )),
226 }
227 }
228 }
229 } else {
230 errors.push(format!("Missing import \"{}\" \"{}\"", key.0, key.1));
233 }
234 }
235}
236
237fn validate_export_fns(
240 export_fns: &HashMap<String, u32>,
241 type_defs: &[FuncType],
242 fn_sigs: &Vec<u32>,
243 interface: &Interface,
244 errors: &mut Vec<String>,
245) {
246 'export_loop: for (key, val) in export_fns.iter() {
247 if let Some(interface_def) = interface.exports.get(key) {
248 let type_sig = if let Some(type_idx) = fn_sigs.get(*val as usize) {
249 if let Some(v) = type_defs.get(*type_idx as usize) {
250 v
251 } else {
252 errors.push(format!(
253 "Export \"{}\" refers to type \"{}\" but only {} types were found",
254 &key,
255 type_idx,
256 fn_sigs.len()
257 ));
258 continue;
259 }
260 } else {
261 errors.push(format!(
262 "Use of undeclared function reference \"{}\" in export \"{}\"",
263 val, &key
264 ));
265 continue;
266 };
267 if let Export::Func { params, result, .. } = interface_def {
268 for (i, param) in type_sig
269 .params()
270 .iter()
271 .cloned()
272 .map(wasmparser_type_into_wasm_type)
273 .enumerate()
274 {
275 match param {
276 Ok(t) => {
277 if params.get(i).is_none() {
278 errors.push(format!("Found {} args but the interface only expects {} for exported function \"{}\"", type_sig.params().len(), params.len(), &key));
279 continue 'export_loop;
280 }
281 if t != params[i] {
282 errors.push(format!(
283 "Type mismatch in params in exported func \"{}\": in argument {}, expected {} found {}",
284 &key, i + 1, params[i], t
285 ));
286 }
287 }
288 Err(e) => errors
289 .push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
290 }
291 }
292 for (i, ret) in type_sig
293 .results()
294 .iter()
295 .cloned()
296 .map(wasmparser_type_into_wasm_type)
297 .enumerate()
298 {
299 match ret {
300 Ok(t) => {
301 if result.get(i).is_none() {
302 errors.push(format!("Found {} returns but the interface only expects {} for exported function \"{}\"", i, params.len(), &key));
303 continue 'export_loop;
304 }
305
306 if t != result[i] {
307 errors.push(format!(
308 "Type mismatch in returns in exported func \"{}\": in return {}, expected {} found {}",
309 &key, i + 1, result[i], t
310 ));
311 }
312 }
313 Err(e) => errors
314 .push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
315 }
316 }
317 }
318 }
319 }
320}
321
322fn validate_export_globals(
325 export_globals: &HashMap<String, u32>,
326 global_types: &Vec<GlobalType>,
327 interface: &Interface,
328 errors: &mut Vec<String>,
329) {
330 for (key, val) in export_globals.iter() {
331 if let Some(Export::Global { var_type, .. }) = interface.exports.get(key) {
332 if global_types.get(*val as usize).is_none() {
333 errors.push(format!(
334 "Invalid wasm, expected {} global types, found {}",
335 val,
336 global_types.len()
337 ));
338 }
339 match wasmparser_type_into_wasm_type(global_types[*val as usize].content_type) {
340 Ok(t) => {
341 if *var_type != t {
342 errors.push(format!(
343 "Type mismatch in global export {}: expected {} found {}",
344 &key, var_type, t
345 ));
346 }
347 }
348 Err(e) => errors.push(format!("In global export {}: {}", &key, e)),
349 }
350 }
351 }
352}
353
354fn wasmparser_type_into_wasm_type(ty: wasmparser::ValType) -> Result<WasmType, String> {
360 use wasmparser::ValType;
361 Ok(match ty {
362 ValType::I32 => WasmType::I32,
363 ValType::I64 => WasmType::I64,
364 ValType::F32 => WasmType::F32,
365 ValType::F64 => WasmType::F64,
366 e => {
367 return Err(format!("Invalid type found: {:?}", e));
368 }
369 })
370}
371
372#[cfg(test)]
373mod validation_tests {
374 use super::*;
375 use crate::parser;
376
377 #[test]
378 fn global_imports() {
379 const WAT: &str = r#"(module
380(type $t0 (func (param i32 i64)))
381(global $length (import "env" "length") i32)
382(import "env" "do_panic" (func $do_panic (type $t0)))
383)"#;
384 let wasm = wat::parse_str(WAT).unwrap();
385
386 let interface_src = r#"
387(interface
388(func (import "env" "do_panic") (param i32 i64))
389(global (import "env" "length") (type i32)))"#;
390 let interface = parser::parse_interface(interface_src).unwrap();
391
392 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
393
394 assert!(result.is_ok());
395
396 let interface_src = r#"
398(interface
399(func (import "env" "do_panic") (param i32 i64))
400(global (import "env" "length") (type i64)))"#;
401 let interface = parser::parse_interface(interface_src).unwrap();
402
403 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
404
405 assert!(
406 result.is_err(),
407 "global import type mismatch causes an error"
408 );
409
410 let interface_src = r#"
412(interface
413(func (import "env" "do_panic") (param i64))
414(global (import "env" "length") (type i32)))"#;
415 let interface = parser::parse_interface(interface_src).unwrap();
416
417 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
418
419 assert!(
420 result.is_err(),
421 "function import type mismatch causes an error"
422 );
423
424 let interface_src = r#"
426(interface
427(func (import "env" "do_panic") (param i64))
428(global (import "env" "length_plus_plus") (type i32)))"#;
429 let interface = parser::parse_interface(interface_src).unwrap();
430
431 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
432
433 assert!(
434 result.is_err(),
435 "all imports must be covered by the interface"
436 );
437 }
438
439 #[test]
440 fn global_exports() {
441 const WAT: &str = r#"(module
442(func (export "as-set_local-first") (param i32) (result i32)
443 (nop) (i32.const 2) (set_local 0) (get_local 0))
444(global (export "num_tries") i64 (i64.const 0))
445)"#;
446 let wasm = wat::parse_str(WAT).unwrap();
447
448 let interface_src = r#"
449(interface
450(func (export "as-set_local-first") (param i32) (result i32))
451(global (export "num_tries") (type i64)))"#;
452 let interface = parser::parse_interface(interface_src).unwrap();
453
454 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
455
456 assert!(result.is_ok());
457
458 let interface_src = r#"
460(interface
461(func (export "as-set_local-first") (param i32) (result i32))
462(global (export "num_tries") (type f32)))"#;
463 let interface = parser::parse_interface(interface_src).unwrap();
464
465 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
466
467 assert!(
468 result.is_err(),
469 "global export type mismatch causes an error"
470 );
471
472 let interface_src = r#"
474(interface
475(func (export "as-set_local-first") (param i64) (result i64))
476(global (export "num_tries") (type i64)))"#;
477 let interface = parser::parse_interface(interface_src).unwrap();
478
479 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
480
481 assert!(
482 result.is_err(),
483 "function export type mismatch causes an error"
484 );
485
486 let interface_src = r#"
488(interface
489(func (export "as-set_local-first") (param i64) (result i64))
490(global (export "numb_trees") (type i64)))"#;
491 let interface = parser::parse_interface(interface_src).unwrap();
492
493 let result = validate_wasm_and_report_errors(&wasm[..], &interface);
494
495 assert!(result.is_err(), "missing a required export is an error");
496 }
497}
498
499#[derive(Debug)]
500pub enum WasmValidationError {
501 InvalidWasm { error: String },
502 InterfaceViolated { errors: Vec<String> },
503 UnsupportedType { error: String },
504}