1#![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
20struct 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
114impl<'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 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
161impl<'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
176impl<'s, S: Source> Validate<'s, S> for Table<'s> {
178 fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
179 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
196impl<'s, S: Source> Validate<'s, S> for Memory<'s> {
198 fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
199 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
235impl<'s, S: Source> Validate<'s, S> for Global<'s> {
237 fn validate<'m>(&self, ctx: &mut Context<'m, 's, S>) -> Result<(), S> {
238 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
250impl<'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
268impl<'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
283impl<'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
303impl<'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 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
339impl<'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 #[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}