use crate::ir::*;
use crate::ValType;
use crate::{DataId, Function, FunctionKind, InitExpr, LocalFunction, Result};
use crate::{Global, GlobalKind, Memory, MemoryId, Module, Table, TableKind};
use failure::{bail, ResultExt};
use rayon::prelude::*;
use std::collections::HashSet;
pub fn run(module: &Module) -> Result<()> {
log::debug!("validating module");
if module.config.only_stable_features {
if module.tables.iter().count() > 1 {
bail!("multiple tables not allowed in the wasm spec yet");
}
if module.memories.iter().count() > 1 {
bail!("multiple memories not allowed in the wasm spec yet");
}
}
for memory in module.memories.iter() {
validate_memory(memory)?;
}
for table in module.tables.iter() {
validate_table(table)?;
}
for global in module.globals.iter() {
validate_global(module, global)?;
}
validate_exports(module)?;
if let Some(start) = module.start {
let ty = module.funcs.get(start).ty();
let ty = module.types.get(ty);
if ty.results().len() > 0 || ty.params().len() > 0 {
bail!("start function must take no arguments and return nothing");
}
}
let errs = module
.funcs
.par_iter()
.map(|function| {
let mut errs = Vec::new();
let local = match &function.kind {
FunctionKind::Local(local) => local,
_ => return Vec::new(),
};
let mut cx = Validate {
errs: &mut errs,
function,
local,
module,
};
local.entry_block().visit(&mut cx);
errs
})
.reduce(Vec::new, |mut a, b| {
a.extend(b);
a
});
if errs.len() == 0 {
return Ok(());
}
let mut msg = format!("errors validating module:\n");
for error in errs {
msg.push_str(&format!(" * {}\n", error));
for cause in error.iter_causes() {
msg.push_str(&format!(" * {}\n", cause));
}
}
bail!("{}", msg)
}
fn validate_memory(m: &Memory) -> Result<()> {
if m.shared && m.maximum.is_none() {
bail!("shared memories must have a maximum size");
}
validate_limits(m.initial, m.maximum, u32::from(u16::max_value()) + 1)
.context("when validating a memory")?;
Ok(())
}
fn validate_table(t: &Table) -> Result<()> {
validate_limits(t.initial, t.maximum, u32::max_value()).context("when validating a table")?;
match t.kind {
TableKind::Function(_) => {}
TableKind::Anyref(_) => {}
}
Ok(())
}
fn validate_limits(initial: u32, maximum: Option<u32>, k: u32) -> Result<()> {
match (initial, maximum) {
(min, Some(max)) if max < min || max > k => {
bail!("invalid limits: min = {}, max = {}; k = {}", min, max, k)
}
(min, _) => {
if min <= k {
Ok(())
} else {
bail!("invalid limits: min = {}, k = {}", min, k)
}
}
}
}
fn validate_exports(module: &Module) -> Result<()> {
let mut exports = HashSet::new();
for export in module.exports.iter() {
if !exports.insert(&export.name) {
bail!("duplicate export of `{}`", export.name)
}
}
Ok(())
}
fn validate_global(module: &Module, global: &Global) -> Result<()> {
match global.kind {
GlobalKind::Import(_) => return Ok(()),
GlobalKind::Local(InitExpr::Value(value)) => {
validate_value(value, global.ty).context("invalid type on global")?;
}
GlobalKind::Local(InitExpr::Global(other)) => {
let other = module.globals.get(other);
match other.kind {
GlobalKind::Import(_) => {}
GlobalKind::Local(_) => {
bail!("initializer for local global must be imported global");
}
}
if other.ty != global.ty {
bail!("locally defined global does not match type of import");
}
}
}
Ok(())
}
fn validate_value(value: Value, ty: ValType) -> Result<()> {
match (value, ty) {
(Value::I32(_), ValType::I32) => {}
(Value::I64(_), ValType::I64) => {}
(Value::F32(_), ValType::F32) => {}
(Value::F64(_), ValType::F64) => {}
(Value::V128(_), ValType::V128) => {}
_ => bail!("mismatched types in value"),
}
Ok(())
}
struct Validate<'a> {
errs: &'a mut Vec<failure::Error>,
function: &'a Function,
local: &'a LocalFunction,
module: &'a Module,
}
impl Validate<'_> {
fn memarg(&mut self, arg: &MemArg, width: u32) {
if arg.align > width {
self.err("memory operation with alignment greater than natural size");
}
}
fn require_shared(&mut self, m: MemoryId) {
let mem = self.module.memories.get(m);
if !mem.shared {
self.err("atomic operations require a shared memory");
}
}
fn require_atomic(&mut self, m: MemoryId, arg: &MemArg, width: u32) {
self.require_shared(m);
if arg.align != width {
self.err("alignment for atomics must be same as natural width");
}
}
fn err(&mut self, msg: &str) {
let mut err = failure::format_err!("{}", msg);
if let Some(name) = &self.function.name {
err = err.context(format!("in function {}", name)).into();
}
self.errs.push(err);
}
}
impl<'a> Visitor<'a> for Validate<'a> {
fn local_function(&self) -> &'a LocalFunction {
self.local
}
fn visit_load(&mut self, e: &Load) {
if e.kind.atomic() {
self.require_atomic(e.memory, &e.arg, e.kind.width());
} else {
self.memarg(&e.arg, e.kind.width());
}
e.visit(self);
}
fn visit_store(&mut self, e: &Store) {
if e.kind.atomic() {
self.require_atomic(e.memory, &e.arg, e.kind.width());
} else {
self.memarg(&e.arg, e.kind.width());
}
e.visit(self);
}
fn visit_atomic_rmw(&mut self, e: &AtomicRmw) {
self.require_atomic(e.memory, &e.arg, e.width.bytes());
e.visit(self);
}
fn visit_cmpxchg(&mut self, e: &Cmpxchg) {
self.require_atomic(e.memory, &e.arg, e.width.bytes());
e.visit(self);
}
fn visit_atomic_notify(&mut self, e: &AtomicNotify) {
self.require_shared(e.memory);
e.visit(self);
}
fn visit_atomic_wait(&mut self, e: &AtomicWait) {
let width = if e.sixty_four { 8 } else { 4 };
self.require_atomic(e.memory, &e.arg, width);
e.visit(self);
}
fn visit_data_id(&mut self, id: &DataId) {
if !self.module.data.get(*id).passive {
self.err("referenced data segment is not passive");
}
}
}