wain_validate/
lib.rs

1// Module validation for Wasm module
2// - https://webassembly.github.io/spec/core/valid/index.html
3// - https://webassembly.github.io/spec/core/appendix/algorithm.html#algo-valid
4#![forbid(unsafe_code)]
5#![warn(clippy::dbg_macro)]
6
7extern crate wain_ast;
8
9mod error;
10mod insn;
11
12pub use error::{Error, Result};
13
14use error::ErrorKind;
15use std::borrow::Cow;
16use std::collections::HashMap;
17use wain_ast::source::Source;
18use wain_ast::*;
19
20// Validation context
21// https://webassembly.github.io/spec/core/valid/conventions.html#context
22struct Context<'module, 'source: 'module, S: Source> {
23    module: &'module Module<'source>,
24    source: &'module S,
25    num_import_globals: usize,
26}
27
28impl<'m, 's, S: Source> Context<'m, 's, S> {
29    pub fn new(module: &'m Module<'s>, source: &'m S) -> Context<'m, 's, S> {
30        let num_import_globals = module
31            .globals
32            .iter()
33            .take_while(|g| matches!(g.kind, GlobalKind::Import(_)))
34            .count();
35        Context {
36            module,
37            source,
38            num_import_globals,
39        }
40    }
41
42    fn error<T>(&self, kind: ErrorKind, when: &'static str, offset: usize) -> Result<T, S> {
43        Err(Error::new(kind, Cow::Borrowed(when), offset, self.source))
44    }
45
46    fn validate_idx<T>(
47        &self,
48        s: &'m [T],
49        idx: u32,
50        what: &'static str,
51        when: &'static str,
52        offset: usize,
53    ) -> Result<&'m T, S> {
54        if let Some(item) = s.get(idx as usize) {
55            Ok(item)
56        } else {
57            self.error(
58                ErrorKind::IndexOutOfBounds {
59                    idx,
60                    upper: s.len(),
61                    what,
62                },
63                when,
64                offset,
65            )
66        }
67    }
68
69    fn type_from_idx(&self, idx: u32, when: &'static str, offset: usize) -> Result<&'m FuncType, S> {
70        self.validate_idx(&self.module.types, idx, "type", when, offset)
71    }
72
73    fn func_from_idx(&self, idx: u32, when: &'static str, offset: usize) -> Result<&'m Func, S> {
74        self.validate_idx(&self.module.funcs, idx, "function", when, offset)
75    }
76
77    fn table_from_idx(&self, idx: u32, when: &'static str, offset: usize) -> Result<&'m Table, S> {
78        self.validate_idx(&self.module.tables, idx, "table", when, offset)
79    }
80
81    fn global_from_idx(&self, idx: u32, when: &'static str, offset: usize) -> Result<&'m Global, S> {
82        self.validate_idx(&self.module.globals, idx, "global variable", when, offset)
83    }
84
85    fn memory_from_idx(&self, idx: u32, when: &'static str, offset: usize) -> Result<&'m Memory, S> {
86        self.validate_idx(&self.module.memories, idx, "memory", when, offset)
87    }
88}
89
90pub fn validate<S: Source>(root: &Root<'_, S>) -> Result<(), S> {
91    let mut ctx = Context::new(&root.module, &root.source);
92    root.module.validate(&mut ctx)
93}
94
95trait Validate<'s, S: Source> {
96    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S>;
97}
98
99impl<'s, S: Source, V: Validate<'s, S>> Validate<'s, S> for Vec<V> {
100    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
101        self.iter().try_for_each(|n| n.validate(ctx))
102    }
103}
104
105impl<'s, S: Source, V: Validate<'s, S>> Validate<'s, S> for Option<V> {
106    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
107        match self {
108            Some(node) => node.validate(ctx),
109            None => Ok(()),
110        }
111    }
112}
113
114// https://webassembly.github.io/spec/core/valid/modules.html#valid-module
115impl<'s, S: Source> Validate<'s, S> for Module<'s> {
116    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
117        self.types.validate(ctx)?;
118        self.funcs.validate(ctx)?;
119        self.tables.validate(ctx)?;
120        self.memories.validate(ctx)?;
121        self.globals.validate(ctx)?;
122        self.elems.validate(ctx)?;
123        self.data.validate(ctx)?;
124        self.entrypoint.validate(ctx)?;
125        self.exports.validate(ctx)?;
126
127        if self.tables.len() > 1 {
128            return ctx.error(
129                ErrorKind::MultipleTables(self.tables.len()),
130                "tables in module",
131                self.start,
132            );
133        }
134        if self.memories.len() > 1 {
135            return ctx.error(
136                ErrorKind::MultipleMemories(self.memories.len()),
137                "memories in module",
138                self.start,
139            );
140        }
141
142        // Export name in module must be unique
143        let mut seen = HashMap::new();
144        for (name, offset) in self.exports.iter().map(|e| (e.name.0.as_ref(), e.start)) {
145            if let Some(prev_offset) = seen.insert(name, offset) {
146                return ctx.error(
147                    ErrorKind::AlreadyExported {
148                        name: name.to_string(),
149                        prev_offset,
150                    },
151                    "exports in module",
152                    offset,
153                );
154            }
155        }
156
157        Ok(())
158    }
159}
160
161// https://webassembly.github.io/spec/core/valid/types.html#valid-functype
162impl<'s, S: Source> Validate<'s, S> for FuncType {
163    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
164        if self.results.len() > 1 {
165            ctx.error(
166                ErrorKind::MultipleReturnTypes(self.results.clone()),
167                "result types in function type",
168                self.start,
169            )
170        } else {
171            Ok(())
172        }
173    }
174}
175
176// https://webassembly.github.io/spec/core/valid/modules.html#tables
177impl<'s, S: Source> Validate<'s, S> for Table<'s> {
178    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
179        // Validation for table type is unnecessary here
180        // https://webassembly.github.io/spec/core/syntax/types.html#syntax-tabletype
181        // Limits should be within 2**32 but the values are already u32. It should be validated by parser
182
183        if let Limits::Range(min, max) = self.ty.limit {
184            if min > max {
185                return ctx.error(
186                    ErrorKind::InvalidLimitRange(min, max),
187                    "limits in table type",
188                    self.start,
189                );
190            }
191        }
192        Ok(())
193    }
194}
195
196// https://webassembly.github.io/spec/core/valid/modules.html#memories
197impl<'s, S: Source> Validate<'s, S> for Memory<'s> {
198    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
199        // https://webassembly.github.io/spec/core/valid/types.html#valid-memtype
200        let limit = 1 << 16;
201        let invalid = match self.ty.limit {
202            Limits::From(min) if min > limit => Some(min),
203            Limits::Range(min, _) if min > limit => Some(min),
204            Limits::Range(_, max) if max > limit => Some(max),
205            _ => None,
206        };
207
208        if let Some(value) = invalid {
209            return ctx.error(
210                ErrorKind::LimitsOutOfRange {
211                    value,
212                    min: 0,
213                    max: limit,
214                    what: "memory type",
215                },
216                "limits in memory",
217                self.start,
218            );
219        }
220
221        if let Limits::Range(min, max) = self.ty.limit {
222            if min > max {
223                return ctx.error(
224                    ErrorKind::InvalidLimitRange(min, max),
225                    "limits in memory type",
226                    self.start,
227                );
228            }
229        }
230
231        Ok(())
232    }
233}
234
235// https://webassembly.github.io/spec/core/valid/modules.html#globals
236impl<'s, S: Source> Validate<'s, S> for Global<'s> {
237    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
238        // https://webassembly.github.io/spec/core/valid/types.html#valid-globaltype
239        // Nothing to do for validating GlobalType
240
241        match &self.kind {
242            GlobalKind::Import(_) => Ok(()),
243            GlobalKind::Init(init) => {
244                crate::insn::validate_constant(init, ctx, self.ty, "init expression for global variable", self.start)
245            }
246        }
247    }
248}
249
250// https://webassembly.github.io/spec/core/valid/modules.html#element-segments
251impl<'s, S: Source> Validate<'s, S> for ElemSegment {
252    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
253        ctx.table_from_idx(self.idx, "element segment", self.start)?;
254        crate::insn::validate_constant(
255            &self.offset,
256            ctx,
257            ValType::I32,
258            "offset expression in element segment",
259            self.start,
260        )?;
261        for funcidx in self.init.iter() {
262            ctx.func_from_idx(*funcidx, "init in element segment", self.start)?;
263        }
264        Ok(())
265    }
266}
267
268// https://webassembly.github.io/spec/core/valid/modules.html#data-segments
269impl<'s, S: Source> Validate<'s, S> for DataSegment<'s> {
270    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
271        ctx.memory_from_idx(self.idx, "data segment", self.start)?;
272        crate::insn::validate_constant(
273            &self.offset,
274            ctx,
275            ValType::I32,
276            "offset expression in data segment",
277            self.start,
278        )?;
279        Ok(())
280    }
281}
282
283// https://webassembly.github.io/spec/core/valid/modules.html#start-function
284impl<'s, S: Source> Validate<'s, S> for StartFunction {
285    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
286        let func = ctx.func_from_idx(self.idx, "start function in module", self.start)?;
287        let fty = ctx.type_from_idx(func.idx, "start function in module", self.start)?;
288        if !fty.params.is_empty() || !fty.results.is_empty() {
289            return ctx.error(
290                ErrorKind::StartFunctionSignature {
291                    idx: self.idx,
292                    params: fty.params.clone(),
293                    results: fty.results.clone(),
294                },
295                "start function in 'start' section",
296                self.start,
297            );
298        }
299        Ok(())
300    }
301}
302
303// https://webassembly.github.io/spec/core/valid/modules.html#exports
304impl<'s, S: Source> Validate<'s, S> for Export<'s> {
305    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
306        match self.kind {
307            ExportKind::Func(idx) => {
308                let func = ctx.func_from_idx(idx, "exported function", self.start)?;
309                if self.name.0 == "_start" {
310                    // Functions were already validated
311                    let fty = &ctx.module.types[func.idx as usize];
312                    if !fty.params.is_empty() || !fty.results.is_empty() {
313                        return ctx.error(
314                            ErrorKind::StartFunctionSignature {
315                                idx,
316                                params: fty.params.clone(),
317                                results: fty.results.clone(),
318                            },
319                            "start function exported as '_start'",
320                            self.start,
321                        );
322                    }
323                }
324            }
325            ExportKind::Table(idx) => {
326                ctx.table_from_idx(idx, "exported table", self.start)?;
327            }
328            ExportKind::Memory(idx) => {
329                ctx.memory_from_idx(idx, "exported memory", self.start)?;
330            }
331            ExportKind::Global(idx) => {
332                ctx.global_from_idx(idx, "exported global variable", self.start)?;
333            }
334        }
335        Ok(())
336    }
337}
338
339// https://webassembly.github.io/spec/core/valid/modules.html#functions
340impl<'s, S: Source> Validate<'s, S> for Func<'s> {
341    fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
342        let func_ty = ctx.type_from_idx(self.idx, "function", self.start)?;
343        match &self.kind {
344            FuncKind::Import(_) => Ok(()),
345            FuncKind::Body { locals, expr } => crate::insn::validate_func_body(expr, func_ty, locals, ctx, self.start),
346        }
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353    use std::fmt;
354    use InsnKind::*;
355
356    #[derive(Clone)]
357    struct DummySource;
358
359    impl Source for DummySource {
360        type Raw = &'static str;
361        fn describe(&self, _: &mut fmt::Formatter<'_>, _: usize) -> fmt::Result {
362            Ok(())
363        }
364        fn raw(&self) -> Self::Raw {
365            ""
366        }
367    }
368
369    fn memory(limit: Limits) -> Memory<'static> {
370        Memory {
371            start: 0,
372            ty: MemType { limit },
373            import: None,
374        }
375    }
376
377    fn func_type(params: Vec<ValType>, ret: Option<ValType>) -> FuncType {
378        let results = if let Some(ret) = ret { vec![ret] } else { vec![] };
379        FuncType {
380            start: 0,
381            params,
382            results,
383        }
384    }
385
386    fn func(idx: u32, locals: Vec<ValType>, expr: Vec<InsnKind>) -> Func<'static> {
387        let expr = expr.into_iter().map(|kind| Instruction { start: 0, kind }).collect();
388        Func {
389            start: 0,
390            idx,
391            kind: FuncKind::Body { locals, expr },
392        }
393    }
394
395    fn root(module: Module<'_>) -> Root<'_, DummySource> {
396        Root {
397            module,
398            source: DummySource,
399        }
400    }
401
402    // From https://github.com/WebAssembly/spec/blob/cc2d59bd56e5342e3c1834a7699915f8b67fc29c/test/core/call.wast#L409-L415
403    #[test]
404    fn values_remain_on_stack_after_function() {
405        let mut m = Module::default();
406        m.memories.push(memory(Limits::From(0)));
407        m.types.push(func_type(vec![], None));
408        m.funcs.push(func(0, vec![], vec![I32Const(1), Call(0)]));
409        let err = validate(&root(m)).unwrap_err();
410        assert!(matches!(
411            err.kind(),
412            ErrorKind::InvalidStackDepth {
413                expected: 0,
414                actual: 1,
415                ..
416            }
417        ));
418    }
419}