use std::collections::HashMap;
use std::error;
use std::ffi::CString;
use std::fmt;
use std::io;
use std::str;
use std::vec;
use serde_json;
use super::{Error as WabtError, Features, Script, WabtBuf, WabtWriteScriptResult};
mod json;
#[derive(Debug)]
pub enum Error {
IoError(io::Error),
WabtError(WabtError),
Other(String),
WithLineInfo {
line: u64,
error: Box<Error>,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::IoError(ref io_err) => write!(f, "IO error: {}", io_err),
Error::WabtError(ref wabt_err) => write!(f, "wabt error: {:?}", wabt_err),
Error::Other(ref message) => write!(f, "{}", message),
Error::WithLineInfo { line, ref error } => write!(f, "At line {}: {}", line, error),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::IoError(ref e) => e.description(),
Error::WabtError(_) => "wabt error",
Error::Other(ref msg) => &msg,
Error::WithLineInfo { ref error, .. } => error.description(),
}
}
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
Error::IoError(ref io_err) => Some(io_err),
Error::WabtError(ref wabt_err) => Some(wabt_err),
Error::Other(_) => None,
Error::WithLineInfo { ref error, .. } => Some(error),
}
}
}
impl From<WabtError> for Error {
fn from(e: WabtError) -> Error {
Error::WabtError(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IoError(e)
}
}
pub trait FromBits<T> {
fn from_bits(other: T) -> Self;
}
impl<T> FromBits<T> for T {
fn from_bits(other: T) -> Self {
other
}
}
impl FromBits<u32> for f32 {
fn from_bits(other: u32) -> Self {
Self::from_bits(other)
}
}
impl FromBits<u64> for f64 {
fn from_bits(other: u64) -> Self {
Self::from_bits(other)
}
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub enum Value<F32 = f32, F64 = f64> {
I32(i32),
I64(i64),
F32(F32),
F64(F64),
V128(u128),
}
impl<F32: FromBits<u32>, F64: FromBits<u64>> Value<F32, F64> {
fn decode_f32(val: u32) -> Self {
Value::F32(F32::from_bits(val))
}
fn decode_f64(val: u64) -> Self {
Value::F64(F64::from_bits(val))
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Action<F32 = f32, F64 = f64> {
Invoke {
module: Option<String>,
field: String,
args: Vec<Value<F32, F64>>,
},
Get {
module: Option<String>,
field: String,
},
}
fn parse_value<F32: FromBits<u32>, F64: FromBits<u64>>(
test_val: &json::RuntimeValue,
) -> Result<Value<F32, F64>, Error> {
fn parse_val<P: str::FromStr>(str_val: &str, str_ty: &str) -> Result<P, Error> {
str_val
.parse()
.map_err(|_| Error::Other(format!("can't parse '{}' as '{}'", str_val, str_ty)))
}
let value = match test_val.value_type.as_ref() {
"i32" => {
let unsigned: u32 = parse_val(&test_val.value, &test_val.value_type)?;
Value::I32(unsigned as i32)
}
"i64" => {
let unsigned: u64 = parse_val(&test_val.value, &test_val.value_type)?;
Value::I64(unsigned as i64)
}
"f32" => {
let unsigned: u32 = parse_val(&test_val.value, &test_val.value_type)?;
Value::decode_f32(unsigned)
}
"f64" => {
let unsigned: u64 = parse_val(&test_val.value, &test_val.value_type)?;
Value::decode_f64(unsigned)
}
"v128" => {
let unsigned: u128 = parse_val(&test_val.value, &test_val.value_type)?;
Value::V128(unsigned)
}
other_ty => {
return Err(Error::Other(format!("Unknown type '{}'", other_ty)));
}
};
Ok(value)
}
fn parse_value_list<F32: FromBits<u32>, F64: FromBits<u64>>(
test_vals: &[json::RuntimeValue],
) -> Result<Vec<Value<F32, F64>>, Error> {
test_vals.iter().map(parse_value).collect()
}
fn parse_action<F32: FromBits<u32>, F64: FromBits<u64>>(
test_action: &json::Action,
) -> Result<Action<F32, F64>, Error> {
let action = match *test_action {
json::Action::Invoke {
ref module,
ref field,
ref args,
} => Action::Invoke {
module: module.to_owned(),
field: field.to_owned(),
args: parse_value_list(args)?,
},
json::Action::Get {
ref module,
ref field,
} => Action::Get {
module: module.to_owned(),
field: field.to_owned(),
},
};
Ok(action)
}
fn wast2json(
source: &[u8],
test_filename: &str,
features: Features,
) -> Result<WabtWriteScriptResult, Error> {
let script = Script::parse(test_filename, source, features.clone())?;
script.resolve_names()?;
script.validate()?;
let result = script.write_binaries(test_filename)?;
Ok(result)
}
#[derive(Clone, Debug)]
pub struct ModuleBinary {
module: Vec<u8>,
}
impl Eq for ModuleBinary {}
impl PartialEq for ModuleBinary {
fn eq(&self, rhs: &Self) -> bool {
self.module == rhs.module
}
}
impl ModuleBinary {
fn from_vec(module: Vec<u8>) -> ModuleBinary {
ModuleBinary { module }
}
pub fn into_vec(self) -> Vec<u8> {
self.module
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CommandKind<F32 = f32, F64 = f64> {
Module {
module: ModuleBinary,
name: Option<String>,
},
AssertReturn {
action: Action<F32, F64>,
expected: Vec<Value<F32, F64>>,
},
AssertReturnCanonicalNan {
action: Action<F32, F64>,
},
AssertReturnArithmeticNan {
action: Action<F32, F64>,
},
AssertTrap {
action: Action<F32, F64>,
message: String,
},
AssertInvalid {
module: ModuleBinary,
message: String,
},
AssertMalformed {
module: ModuleBinary,
message: String,
},
AssertUninstantiable {
module: ModuleBinary,
message: String,
},
AssertExhaustion {
action: Action<F32, F64>,
message: String,
},
AssertUnlinkable {
module: ModuleBinary,
message: String,
},
Register {
name: Option<String>,
as_name: String,
},
PerformAction(Action<F32, F64>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Command<F32 = f32, F64 = f64> {
pub line: u64,
pub kind: CommandKind<F32, F64>,
}
pub struct ScriptParser<F32 = f32, F64 = f64> {
cmd_iter: vec::IntoIter<json::Command>,
modules: HashMap<CString, WabtBuf>,
_phantom: ::std::marker::PhantomData<(F32, F64)>,
}
impl<F32: FromBits<u32>, F64: FromBits<u64>> ScriptParser<F32, F64> {
pub fn from_source_and_name(source: &[u8], test_filename: &str) -> Result<Self, Error> {
ScriptParser::from_source_and_name_with_features(source, test_filename, Features::new())
}
pub fn from_source_and_name_with_features(
source: &[u8],
test_filename: &str,
features: Features,
) -> Result<Self, Error> {
if !test_filename.ends_with(".wast") {
return Err(Error::Other(format!(
"Provided {} should have .wast extension",
test_filename
)));
}
let results = wast2json(source, test_filename, features.clone())?;
let results = results.take_all().expect("Failed to release");
let json_str = results.json_output_buffer.as_ref();
let spec: json::Spec =
serde_json::from_slice(json_str).expect("Failed to deserialize JSON buffer");
let json::Spec { commands, .. } = spec;
Ok(ScriptParser {
cmd_iter: commands.into_iter(),
modules: results.module_output_buffers,
_phantom: Default::default(),
})
}
pub fn from_str(source: &str) -> Result<Self, Error> {
ScriptParser::from_source_and_name(source.as_bytes(), "test.wast")
}
pub fn next(&mut self) -> Result<Option<Command<F32, F64>>, Error> {
let command = match self.cmd_iter.next() {
Some(cmd) => cmd,
None => return Ok(None),
};
let get_module = |filename: String, s: &Self| {
let filename = CString::new(filename).unwrap();
s.modules
.get(&filename)
.map(|module| ModuleBinary::from_vec(module.as_ref().to_owned()))
.expect("Module referenced in JSON does not exist.")
};
let (line, kind) = match command {
json::Command::Module {
line,
name,
filename,
} => (
line,
CommandKind::Module {
module: get_module(filename, self),
name,
},
),
json::Command::AssertReturn {
line,
action,
expected,
} => (
line,
CommandKind::AssertReturn {
action: parse_action(&action)?,
expected: parse_value_list(&expected)?,
},
),
json::Command::AssertReturnCanonicalNan { line, action } => (
line,
CommandKind::AssertReturnCanonicalNan {
action: parse_action(&action)?,
},
),
json::Command::AssertReturnArithmeticNan { line, action } => (
line,
CommandKind::AssertReturnArithmeticNan {
action: parse_action(&action)?,
},
),
json::Command::AssertExhaustion { line, action, text } => (
line,
CommandKind::AssertExhaustion {
action: parse_action(&action)?,
message: text,
},
),
json::Command::AssertTrap { line, action, text } => (
line,
CommandKind::AssertTrap {
action: parse_action(&action)?,
message: text,
},
),
json::Command::AssertInvalid {
line,
filename,
text,
} => (
line,
CommandKind::AssertInvalid {
module: get_module(filename, self),
message: text,
},
),
json::Command::AssertMalformed {
line,
filename,
text,
} => (
line,
CommandKind::AssertMalformed {
module: get_module(filename, self),
message: text,
},
),
json::Command::AssertUnlinkable {
line,
filename,
text,
} => (
line,
CommandKind::AssertUnlinkable {
module: get_module(filename, self),
message: text,
},
),
json::Command::AssertUninstantiable {
line,
filename,
text,
} => (
line,
CommandKind::AssertUninstantiable {
module: get_module(filename, self),
message: text,
},
),
json::Command::Register {
line,
name,
as_name,
} => (line, CommandKind::Register { name, as_name }),
json::Command::Action { line, action } => {
(line, CommandKind::PerformAction(parse_action(&action)?))
}
};
Ok(Some(Command { line, kind }))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wat2wasm;
#[test]
fn assert_exhaustion() {
const EXHAUSTION: &str = r#"
(module
(func (export "foo")
call 0))
(assert_exhaustion (invoke "foo") "so exhausted")
"#;
let mut script = ScriptParser::<f32, f64>::from_str(EXHAUSTION).unwrap();
assert_eq!(
script.next().unwrap().unwrap(),
Command {
line: 2,
kind: CommandKind::Module {
module: ModuleBinary::from_vec(
wat2wasm(r#"(func (export "foo") call 0)"#).unwrap()
),
name: None,
},
}
);
assert_eq!(
script.next().unwrap().unwrap(),
Command {
line: 6,
kind: CommandKind::AssertExhaustion {
action: Action::Invoke {
module: None,
field: "foo".into(),
args: vec![],
},
message: "so exhausted".into(),
},
}
);
}
#[test]
fn utf8_handling() {
let wast = r#"
(module
;; Test that names are case-sensitive.
(func (export "a") (result i32) (i32.const 13))
(func (export "A") (result i32) (i32.const 14))
;; Test that UTF-8 BOM code points can appear in identifiers.
(func (export "") (result i32) (i32.const 15))
;; Test that Unicode normalization is not applied. These function names
;; contain different codepoints which normalize to the same thing under
;; NFC or NFD.
(func (export "Å") (result i32) (i32.const 16))
(func (export "Å") (result i32) (i32.const 17))
(func (export "Å") (result i32) (i32.const 18))
)
(assert_return (invoke "a") (i32.const 13))
(assert_return (invoke "A") (i32.const 14))
(assert_return (invoke "") (i32.const 15))
(assert_return (invoke "Å") (i32.const 16))
(assert_return (invoke "Å") (i32.const 17))
(assert_return (invoke "Å") (i32.const 18))
"#;
let mut parser: ScriptParser = ScriptParser::from_str(wast).unwrap();
while let Some(Command { .. }) = parser.next().unwrap() {}
}
}