feat: add a histrical wit-bindgen

This commit is contained in:
2023-01-01 00:25:48 +08:00
parent 01e8f5a959
commit aa50d63aec
419 changed files with 45283 additions and 1 deletions

View File

@@ -0,0 +1,13 @@
use clap::Parser;
use wit_component::cli::WasmToWitApp;
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.format_target(false)
.init();
if let Err(e) = WasmToWitApp::parse().execute() {
log::error!("{:?}", e);
std::process::exit(1);
}
}

View File

@@ -0,0 +1,13 @@
use clap::Parser;
use wit_component::cli::WitComponentApp;
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.format_target(false)
.init();
if let Err(e) = WitComponentApp::parse().execute() {
log::error!("{:?}", e);
std::process::exit(1);
}
}

View File

@@ -0,0 +1,13 @@
use clap::Parser;
use wit_component::cli::WitToWasmApp;
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.format_target(false)
.init();
if let Err(e) = WitToWasmApp::parse().execute() {
log::error!("{:?}", e);
std::process::exit(1);
}
}

View File

@@ -0,0 +1,215 @@
//! The WebAssembly component tool command line interface.
#![deny(missing_docs)]
use crate::{
decode_interface_component, ComponentEncoder, InterfaceEncoder, InterfacePrinter,
StringEncoding,
};
use anyhow::{bail, Context, Result};
use clap::Parser;
use std::path::{Path, PathBuf};
use wit_parser::Interface;
fn parse_named_interface(s: &str) -> Result<Interface> {
let (name, path) = s
.split_once('=')
.ok_or_else(|| anyhow::anyhow!("expected a value with format `NAME=INTERFACE`"))?;
parse_interface(Some(name.to_string()), Path::new(path))
}
fn parse_unnamed_interface(s: &str) -> Result<Interface> {
parse_interface(None, Path::new(s))
}
fn parse_interface(name: Option<String>, path: &Path) -> Result<Interface> {
if !path.is_file() {
bail!("interface file `{}` does not exist", path.display(),);
}
let mut interface = Interface::parse_file(&path)
.with_context(|| format!("failed to parse interface file `{}`", path.display()))?;
interface.name = name.unwrap_or_else(|| "".to_string());
Ok(interface)
}
/// WebAssembly component encoder.
///
/// Encodes a WebAssembly component from a core WebAssembly module.
#[derive(Debug, Parser)]
#[clap(name = "component-encoder", version = env!("CARGO_PKG_VERSION"))]
pub struct WitComponentApp {
/// The path to an interface definition file the component imports.
#[clap(long = "import", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))]
pub imports: Vec<Interface>,
/// The path to an interface definition file the component exports.
#[clap(long = "export", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))]
pub exports: Vec<Interface>,
/// The path of the output WebAssembly component.
#[clap(long, short = 'o', value_name = "OUTPUT")]
pub output: Option<PathBuf>,
/// The default interface the component exports.
#[clap(long, short = 'i', value_name = "INTERFACE", parse(try_from_str = parse_unnamed_interface))]
pub interface: Option<Interface>,
/// Skip validation of the output component.
#[clap(long)]
pub skip_validation: bool,
/// The expected string encoding format for the component.
/// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`.
#[clap(long, value_name = "ENCODING")]
pub encoding: Option<StringEncoding>,
/// Path to the WebAssembly module to encode.
#[clap(index = 1, value_name = "MODULE")]
pub module: PathBuf,
}
impl WitComponentApp {
/// Executes the application.
pub fn execute(self) -> Result<()> {
if !self.module.is_file() {
bail!(
"module `{}` does not exist as a file",
self.module.display()
);
}
let output = self.output.unwrap_or_else(|| {
let mut stem: PathBuf = self.module.file_stem().unwrap().into();
stem.set_extension("wasm");
stem
});
let module = wat::parse_file(&self.module)
.with_context(|| format!("failed to parse module `{}`", self.module.display()))?;
let mut encoder = ComponentEncoder::default()
.module(&module)
.imports(&self.imports)
.exports(&self.exports)
.validate(!self.skip_validation);
if let Some(interface) = &self.interface {
encoder = encoder.interface(interface);
}
if let Some(encoding) = &self.encoding {
encoder = encoder.encoding(*encoding);
}
let bytes = encoder.encode().with_context(|| {
format!(
"failed to encode a component from module `{}`",
self.module.display()
)
})?;
std::fs::write(&output, bytes)
.with_context(|| format!("failed to write output file `{}`", output.display()))?;
println!("encoded component `{}`", output.display());
Ok(())
}
}
/// WebAssembly interface encoder.
///
/// Encodes a WebAssembly interface as a WebAssembly component.
#[derive(Debug, Parser)]
#[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))]
pub struct WitToWasmApp {
/// The path of the output WebAssembly component.
#[clap(long, short = 'o', value_name = "OUTPUT")]
pub output: Option<PathBuf>,
/// The path to the WebAssembly interface file to encode.
#[clap(index = 1, value_name = "INTERFACE")]
pub interface: PathBuf,
}
impl WitToWasmApp {
/// Executes the application.
pub fn execute(self) -> Result<()> {
let output = self.output.unwrap_or_else(|| {
let mut stem: PathBuf = self.interface.file_stem().unwrap().into();
stem.set_extension("wasm");
stem
});
let interface = parse_interface(None, &self.interface)?;
let encoder = InterfaceEncoder::new(&interface).validate(true);
let bytes = encoder.encode().with_context(|| {
format!(
"failed to encode a component from interface `{}`",
self.interface.display()
)
})?;
std::fs::write(&output, bytes)
.with_context(|| format!("failed to write output file `{}`", output.display()))?;
println!("encoded interface as component `{}`", output.display());
Ok(())
}
}
/// WebAssembly interface decoder.
///
/// Decodes a WebAssembly interface from a WebAssembly component.
#[derive(Debug, Parser)]
#[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))]
pub struct WasmToWitApp {
/// The path of the output WebAssembly interface file.
#[clap(long, short = 'o', value_name = "OUTPUT")]
pub output: Option<PathBuf>,
/// The path to the WebAssembly component to decode.
#[clap(index = 1, value_name = "COMPONENT")]
pub component: PathBuf,
}
impl WasmToWitApp {
/// Executes the application.
pub fn execute(self) -> Result<()> {
let output = self.output.unwrap_or_else(|| {
let mut stem: PathBuf = self.component.file_stem().unwrap().into();
stem.set_extension("wit");
stem
});
if !self.component.is_file() {
bail!(
"component `{}` does not exist as a file",
self.component.display()
);
}
let bytes = wat::parse_file(&self.component)
.with_context(|| format!("failed to parse component `{}`", self.component.display()))?;
let interface = decode_interface_component(&bytes).with_context(|| {
format!("failed to decode component `{}`", self.component.display())
})?;
let mut printer = InterfacePrinter::default();
std::fs::write(&output, printer.print(&interface)?)
.with_context(|| format!("failed to write output file `{}`", output.display()))?;
println!("decoded interface to `{}`", output.display());
Ok(())
}
}

View File

@@ -0,0 +1,549 @@
use anyhow::{anyhow, bail, Context, Result};
use indexmap::IndexMap;
use wasmparser::{
types, Chunk, ComponentExternalKind, Encoding, Parser, Payload, PrimitiveValType, Validator,
WasmFeatures,
};
use wit_parser::*;
/// Represents information about a decoded WebAssembly component.
pub struct ComponentInfo<'a> {
/// The types defined in the component.
pub types: types::Types,
/// The exported types in the component.
pub exported_types: Vec<(&'a str, u32)>,
/// The exported functions in the component.
pub exported_functions: Vec<(&'a str, u32)>,
}
impl<'a> ComponentInfo<'a> {
/// Creates a new component info by parsing the given WebAssembly component bytes.
pub fn new(mut bytes: &'a [u8]) -> Result<Self> {
let mut parser = Parser::new(0);
let mut parsers = Vec::new();
let mut validator = Validator::new_with_features(WasmFeatures {
component_model: true,
..Default::default()
});
let mut exported_types = Vec::new();
let mut exported_functions = Vec::new();
loop {
match parser.parse(bytes, true)? {
Chunk::Parsed { payload, consumed } => {
bytes = &bytes[consumed..];
match payload {
Payload::Version {
num,
encoding,
range,
} => {
if parsers.is_empty() && encoding != Encoding::Component {
bail!("file is not a WebAssembly component");
}
validator.version(num, encoding, &range)?;
}
Payload::TypeSection(s) => {
validator.type_section(&s)?;
}
Payload::ImportSection(s) => {
validator.import_section(&s)?;
}
Payload::FunctionSection(s) => {
validator.function_section(&s)?;
}
Payload::TableSection(s) => {
validator.table_section(&s)?;
}
Payload::MemorySection(s) => {
validator.memory_section(&s)?;
}
Payload::TagSection(s) => {
validator.tag_section(&s)?;
}
Payload::GlobalSection(s) => {
validator.global_section(&s)?;
}
Payload::ExportSection(s) => {
validator.export_section(&s)?;
}
Payload::StartSection { func, range } => {
validator.start_section(func, &range)?;
}
Payload::ElementSection(s) => {
validator.element_section(&s)?;
}
Payload::DataCountSection { count, range } => {
validator.data_count_section(count, &range)?;
}
Payload::DataSection(s) => {
validator.data_section(&s)?;
}
Payload::CodeSectionStart { count, range, .. } => {
validator.code_section_start(count, &range)?;
}
Payload::CodeSectionEntry(f) => {
validator.code_section_entry(&f)?;
}
Payload::ModuleSection {
parser: inner,
range,
} => {
validator.module_section(&range)?;
parsers.push(parser);
parser = inner;
}
Payload::InstanceSection(s) => {
validator.instance_section(&s)?;
}
Payload::AliasSection(s) => {
validator.alias_section(&s)?;
}
Payload::CoreTypeSection(s) => {
validator.core_type_section(&s)?;
}
Payload::ComponentSection {
parser: inner,
range,
} => {
validator.component_section(&range)?;
parsers.push(parser);
parser = inner;
}
Payload::ComponentInstanceSection(s) => {
validator.component_instance_section(&s)?;
}
Payload::ComponentAliasSection(s) => {
validator.component_alias_section(&s)?;
}
Payload::ComponentTypeSection(s) => {
validator.component_type_section(&s)?;
}
Payload::ComponentCanonicalSection(s) => {
validator.component_canonical_section(&s)?;
}
Payload::ComponentStartSection(s) => {
validator.component_start_section(&s)?;
}
Payload::ComponentImportSection(s) => {
validator.component_import_section(&s)?;
}
Payload::ComponentExportSection(s) => {
validator.component_export_section(&s)?;
if parsers.is_empty() {
for export in s {
let export = export?;
match export.kind {
ComponentExternalKind::Func => {
exported_functions.push((export.name, export.index));
}
ComponentExternalKind::Type => {
exported_types.push((export.name, export.index));
}
_ => {}
}
}
}
}
Payload::CustomSection { .. } => {
// Skip custom sections
}
Payload::UnknownSection { id, range, .. } => {
validator.unknown_section(id, &range)?;
}
Payload::End(offset) => {
let types = validator.end(offset)?;
match parsers.pop() {
Some(parent) => parser = parent,
None => {
return Ok(Self {
types,
exported_types,
exported_functions,
});
}
}
}
}
}
Chunk::NeedMoreData(_) => unreachable!(),
}
}
}
}
/// Represents an interface decoder for WebAssembly components.
pub struct InterfaceDecoder<'a> {
info: &'a ComponentInfo<'a>,
interface: Interface,
type_map: IndexMap<types::TypeId, Type>,
name_map: IndexMap<types::TypeId, &'a str>,
}
impl<'a> InterfaceDecoder<'a> {
/// Creates a new interface decoder for the given component information.
pub fn new(info: &'a ComponentInfo<'a>) -> Self {
Self {
info,
interface: Interface::default(),
name_map: IndexMap::new(),
type_map: IndexMap::new(),
}
}
/// Consumes the decoder and returns the interface representation.
pub fn decode(mut self) -> Result<Interface> {
// Populate names in the name map first
for (name, index) in &self.info.exported_types {
if let types::Type::Defined(_) = self.info.types.type_at(*index, false).unwrap() {
self.name_map.insert(
self.info.types.id_from_type_index(*index, false).unwrap(),
name,
);
}
}
for (name, index) in &self.info.exported_types {
let ty = match self.info.types.type_at(*index, false).unwrap() {
types::Type::ComponentFunc(ty) => ty,
_ => continue,
};
self.add_function(name, ty)?;
}
for (name, index) in &self.info.exported_functions {
let ty = self.info.types.component_function_at(*index).unwrap();
self.add_function(name, ty)?;
}
Ok(self.interface)
}
fn add_function(&mut self, func_name: &str, ty: &types::ComponentFuncType) -> Result<()> {
validate_id(func_name)
.with_context(|| format!("function name `{}` is not a valid identifier", func_name))?;
let mut params = Vec::new();
for (name, ty) in ty.params.iter() {
let name = name
.as_ref()
.ok_or_else(|| anyhow!("function `{}` has a parameter without a name", func_name))?
.clone();
validate_id(&name).with_context(|| {
format!(
"function `{}` has a parameter `{}` that is not a valid identifier",
func_name, name
)
})?;
params.push((name, self.decode_type(ty)?));
}
let result = self.decode_type(&ty.result)?;
self.interface.functions.push(Function {
is_async: false,
docs: Docs::default(),
name: func_name.to_string(),
kind: FunctionKind::Freestanding,
params,
result,
});
Ok(())
}
fn decode_type(&mut self, ty: &types::ComponentValType) -> Result<Type> {
Ok(match ty {
types::ComponentValType::Primitive(ty) => self.decode_primitive(*ty)?,
types::ComponentValType::Type(id) => {
if let Some(ty) = self.type_map.get(id) {
return Ok(*ty);
}
let name = self.name_map.get(id).map(ToString::to_string);
if let Some(name) = name.as_deref() {
validate_id(name).with_context(|| {
format!("type name `{}` is not a valid identifier", name)
})?;
}
let ty = match &self.info.types.type_from_id(*id).unwrap() {
types::Type::Defined(ty) => match ty {
types::ComponentDefinedType::Primitive(ty) => {
self.decode_named_primitive(name, ty)?
}
types::ComponentDefinedType::Record(r) => {
self.decode_record(name, r.fields.iter())?
}
types::ComponentDefinedType::Variant(v) => {
self.decode_variant(name, v.cases.iter())?
}
types::ComponentDefinedType::List(ty) => {
let inner = self.decode_type(ty)?;
Type::Id(self.alloc_type(name, TypeDefKind::List(inner)))
}
types::ComponentDefinedType::Tuple(t) => {
self.decode_tuple(name, &t.types)?
}
types::ComponentDefinedType::Flags(names) => {
self.decode_flags(name, names.iter())?
}
types::ComponentDefinedType::Enum(names) => {
self.decode_enum(name, names.iter())?
}
types::ComponentDefinedType::Union(u) => {
self.decode_union(name, &u.types)?
}
types::ComponentDefinedType::Option(ty) => self.decode_option(name, ty)?,
types::ComponentDefinedType::Expected(ok, err) => {
self.decode_expected(name, ok, err)?
}
},
_ => unreachable!(),
};
self.type_map.insert(*id, ty);
ty
}
})
}
fn decode_named_primitive(
&mut self,
name: Option<String>,
ty: &PrimitiveValType,
) -> Result<Type> {
let mut ty = self.decode_primitive(*ty)?;
if let Some(name) = name {
validate_id(&name)
.with_context(|| format!("type name `{}` is not a valid identifier", name))?;
ty = Type::Id(self.alloc_type(Some(name), TypeDefKind::Type(ty)));
}
Ok(ty)
}
fn decode_primitive(&mut self, ty: PrimitiveValType) -> Result<Type> {
Ok(match ty {
PrimitiveValType::Unit => Type::Unit,
PrimitiveValType::Bool => Type::Bool,
PrimitiveValType::S8 => Type::S8,
PrimitiveValType::U8 => Type::U8,
PrimitiveValType::S16 => Type::S16,
PrimitiveValType::U16 => Type::U16,
PrimitiveValType::S32 => Type::S32,
PrimitiveValType::U32 => Type::U32,
PrimitiveValType::S64 => Type::S64,
PrimitiveValType::U64 => Type::U64,
PrimitiveValType::Float32 => Type::Float32,
PrimitiveValType::Float64 => Type::Float64,
PrimitiveValType::Char => Type::Char,
PrimitiveValType::String => Type::String,
})
}
fn decode_record(
&mut self,
record_name: Option<String>,
fields: impl ExactSizeIterator<Item = (&'a String, &'a types::ComponentValType)>,
) -> Result<Type> {
let record_name =
record_name.ok_or_else(|| anyhow!("interface has an unnamed record type"))?;
let record = Record {
fields: fields
.map(|(name, ty)| {
validate_id(name).with_context(|| {
format!(
"record `{}` has a field `{}` that is not a valid identifier",
record_name, name
)
})?;
Ok(Field {
docs: Docs::default(),
name: name.to_string(),
ty: self.decode_type(ty)?,
})
})
.collect::<Result<_>>()?,
};
Ok(Type::Id(self.alloc_type(
Some(record_name),
TypeDefKind::Record(record),
)))
}
fn decode_variant(
&mut self,
variant_name: Option<String>,
cases: impl ExactSizeIterator<Item = (&'a String, &'a types::VariantCase)>,
) -> Result<Type> {
let variant_name =
variant_name.ok_or_else(|| anyhow!("interface has an unnamed variant type"))?;
let variant = Variant {
cases: cases
.map(|(name, case)| {
validate_id(name).with_context(|| {
format!(
"variant `{}` has a case `{}` that is not a valid identifier",
variant_name, name
)
})?;
Ok(Case {
docs: Docs::default(),
name: name.to_string(),
ty: self.decode_type(&case.ty)?,
})
})
.collect::<Result<_>>()?,
};
Ok(Type::Id(self.alloc_type(
Some(variant_name),
TypeDefKind::Variant(variant),
)))
}
fn decode_tuple(
&mut self,
name: Option<String>,
tys: &[types::ComponentValType],
) -> Result<Type> {
let tuple = Tuple {
types: tys
.iter()
.map(|ty| self.decode_type(ty))
.collect::<Result<_>>()?,
};
Ok(Type::Id(self.alloc_type(name, TypeDefKind::Tuple(tuple))))
}
fn decode_flags(
&mut self,
flags_name: Option<String>,
names: impl ExactSizeIterator<Item = &'a String>,
) -> Result<Type> {
let flags_name =
flags_name.ok_or_else(|| anyhow!("interface has an unnamed flags type"))?;
let flags = Flags {
flags: names
.map(|name| {
validate_id(name).with_context(|| {
format!(
"flags `{}` has a flag named `{}` that is not a valid identifier",
flags_name, name
)
})?;
Ok(Flag {
docs: Docs::default(),
name: name.clone(),
})
})
.collect::<Result<_>>()?,
};
Ok(Type::Id(
self.alloc_type(Some(flags_name), TypeDefKind::Flags(flags)),
))
}
fn decode_enum(
&mut self,
enum_name: Option<String>,
names: impl ExactSizeIterator<Item = &'a String>,
) -> Result<Type> {
let enum_name = enum_name.ok_or_else(|| anyhow!("interface has an unnamed enum type"))?;
let enum_ = Enum {
cases: names
.map(|name| {
validate_id(name).with_context(|| {
format!(
"enum `{}` has a value `{}` that is not a valid identifier",
enum_name, name
)
})?;
Ok(EnumCase {
docs: Docs::default(),
name: name.to_string(),
})
})
.collect::<Result<_>>()?,
};
Ok(Type::Id(
self.alloc_type(Some(enum_name), TypeDefKind::Enum(enum_)),
))
}
fn decode_union(
&mut self,
name: Option<String>,
tys: &[types::ComponentValType],
) -> Result<Type> {
let union = Union {
cases: tys
.iter()
.map(|ty| {
Ok(UnionCase {
docs: Docs::default(),
ty: self.decode_type(ty)?,
})
})
.collect::<Result<_>>()?,
};
Ok(Type::Id(self.alloc_type(name, TypeDefKind::Union(union))))
}
fn decode_option(
&mut self,
name: Option<String>,
payload: &types::ComponentValType,
) -> Result<Type> {
let payload = self.decode_type(payload)?;
Ok(Type::Id(
self.alloc_type(name, TypeDefKind::Option(payload)),
))
}
fn decode_expected(
&mut self,
name: Option<String>,
ok: &types::ComponentValType,
err: &types::ComponentValType,
) -> Result<Type> {
let ok = self.decode_type(ok)?;
let err = self.decode_type(err)?;
Ok(Type::Id(self.alloc_type(
name,
TypeDefKind::Expected(Expected { ok, err }),
)))
}
fn alloc_type(&mut self, name: Option<String>, kind: TypeDefKind) -> TypeId {
self.interface.types.alloc(TypeDef {
docs: Docs::default(),
kind,
name,
foreign_module: None,
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
//! The WebAssembly component tooling.
#![deny(missing_docs)]
use anyhow::{bail, Result};
use std::str::FromStr;
use wasm_encoder::CanonicalOption;
use wit_parser::Interface;
#[cfg(feature = "cli")]
pub mod cli;
mod decoding;
mod encoding;
mod printing;
mod validation;
pub use encoding::*;
pub use printing::*;
/// Supported string encoding formats.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum StringEncoding {
/// Strings are encoded with UTF-8.
UTF8,
/// Strings are encoded with UTF-16.
UTF16,
/// Strings are encoded with compact UTF-16 (i.e. Latin1+UTF-16).
CompactUTF16,
}
impl Default for StringEncoding {
fn default() -> Self {
StringEncoding::UTF8
}
}
impl FromStr for StringEncoding {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"utf8" => Ok(StringEncoding::UTF8),
"utf16" => Ok(StringEncoding::UTF16),
"compact-utf16" => Ok(StringEncoding::CompactUTF16),
_ => bail!("unknown string encoding `{}`", s),
}
}
}
impl From<StringEncoding> for wasm_encoder::CanonicalOption {
fn from(e: StringEncoding) -> wasm_encoder::CanonicalOption {
match e {
StringEncoding::UTF8 => CanonicalOption::UTF8,
StringEncoding::UTF16 => CanonicalOption::UTF16,
StringEncoding::CompactUTF16 => CanonicalOption::CompactUTF16,
}
}
}
/// Decode an "interface-only" component to a wit `Interface`.
pub fn decode_interface_component(bytes: &[u8]) -> Result<Interface> {
decoding::InterfaceDecoder::new(&decoding::ComponentInfo::new(bytes)?).decode()
}

View File

@@ -0,0 +1,381 @@
use anyhow::{bail, Result};
use indexmap::IndexSet;
use std::fmt::Write;
use wit_parser::{
Enum, Expected, Flags, Interface, Record, Tuple, Type, TypeDefKind, TypeId, Union, Variant,
};
/// A utility for printing WebAssembly interface definitions to a string.
#[derive(Default)]
pub struct InterfacePrinter {
output: String,
declared: IndexSet<TypeId>,
}
impl InterfacePrinter {
/// Print the given WebAssembly interface to a string.
pub fn print(&mut self, interface: &Interface) -> Result<String> {
for func in &interface.functions {
for ty in func.params.iter().map(|p| &p.1).chain([&func.result]) {
self.declare_type(interface, ty)?;
}
}
for func in &interface.functions {
write!(&mut self.output, "{}: func(", func.name)?;
for (i, (name, ty)) in func.params.iter().enumerate() {
if i > 0 {
self.output.push_str(", ");
}
write!(&mut self.output, "{}: ", name)?;
self.print_type_name(interface, ty)?;
}
self.output.push(')');
match &func.result {
Type::Unit => {}
other => {
self.output.push_str(" -> ");
self.print_type_name(interface, other)?;
}
}
self.output.push_str("\n\n");
}
self.declared.clear();
Ok(std::mem::take(&mut self.output))
}
fn print_type_name(&mut self, interface: &Interface, ty: &Type) -> Result<()> {
match ty {
Type::Unit => self.output.push_str("unit"),
Type::Bool => self.output.push_str("bool"),
Type::U8 => self.output.push_str("u8"),
Type::U16 => self.output.push_str("u16"),
Type::U32 => self.output.push_str("u32"),
Type::U64 => self.output.push_str("u64"),
Type::S8 => self.output.push_str("s8"),
Type::S16 => self.output.push_str("s16"),
Type::S32 => self.output.push_str("s32"),
Type::S64 => self.output.push_str("s64"),
Type::Float32 => self.output.push_str("float32"),
Type::Float64 => self.output.push_str("float64"),
Type::Char => self.output.push_str("char"),
Type::String => self.output.push_str("string"),
Type::Id(id) => {
let ty = &interface.types[*id];
if let Some(name) = &ty.name {
self.output.push_str(name);
return Ok(());
}
match &ty.kind {
TypeDefKind::Tuple(t) => {
self.print_tuple_type(interface, t)?;
}
TypeDefKind::Option(t) => {
self.print_option_type(interface, t)?;
}
TypeDefKind::Expected(t) => {
self.print_expected_type(interface, t)?;
}
TypeDefKind::Record(_) => {
bail!("interface has an unnamed record type");
}
TypeDefKind::Flags(_) => {
bail!("interface has unnamed flags type")
}
TypeDefKind::Enum(_) => {
bail!("interface has unnamed enum type")
}
TypeDefKind::Variant(_) => {
bail!("interface has unnamed variant type")
}
TypeDefKind::Union(_) => {
bail!("interface has unnamed union type")
}
TypeDefKind::List(ty) => {
self.output.push_str("list<");
self.print_type_name(interface, ty)?;
self.output.push('>');
}
TypeDefKind::Type(ty) => self.print_type_name(interface, ty)?,
TypeDefKind::Future(_) => {
todo!("interface has an unnamed future type")
}
TypeDefKind::Stream(_) => {
todo!("interface has an unnamed stream type")
}
}
}
Type::Handle(_) => bail!("interface has unsupported type"),
}
Ok(())
}
fn print_tuple_type(&mut self, interface: &Interface, tuple: &Tuple) -> Result<()> {
self.output.push_str("tuple<");
for (i, ty) in tuple.types.iter().enumerate() {
if i > 0 {
self.output.push_str(", ");
}
self.print_type_name(interface, ty)?;
}
self.output.push('>');
Ok(())
}
fn print_option_type(&mut self, interface: &Interface, payload: &Type) -> Result<()> {
self.output.push_str("option<");
self.print_type_name(interface, payload)?;
self.output.push('>');
Ok(())
}
fn print_expected_type(&mut self, interface: &Interface, expected: &Expected) -> Result<()> {
self.output.push_str("expected<");
self.print_type_name(interface, &expected.ok)?;
self.output.push_str(", ");
self.print_type_name(interface, &expected.err)?;
self.output.push('>');
Ok(())
}
fn declare_type(&mut self, interface: &Interface, ty: &Type) -> Result<()> {
match ty {
Type::Unit
| Type::Bool
| Type::U8
| Type::U16
| Type::U32
| Type::U64
| Type::S8
| Type::S16
| Type::S32
| Type::S64
| Type::Float32
| Type::Float64
| Type::Char
| Type::String => return Ok(()),
Type::Id(id) => {
if !self.declared.insert(*id) {
return Ok(());
}
let ty = &interface.types[*id];
match &ty.kind {
TypeDefKind::Record(r) => {
self.declare_record(interface, ty.name.as_deref(), r)?
}
TypeDefKind::Tuple(t) => {
self.declare_tuple(interface, ty.name.as_deref(), t)?
}
TypeDefKind::Flags(f) => self.declare_flags(ty.name.as_deref(), f)?,
TypeDefKind::Variant(v) => {
self.declare_variant(interface, ty.name.as_deref(), v)?
}
TypeDefKind::Union(u) => {
self.declare_union(interface, ty.name.as_deref(), u)?
}
TypeDefKind::Option(t) => {
self.declare_option(interface, ty.name.as_deref(), t)?
}
TypeDefKind::Expected(e) => {
self.declare_expected(interface, ty.name.as_deref(), e)?
}
TypeDefKind::Enum(e) => self.declare_enum(ty.name.as_deref(), e)?,
TypeDefKind::List(inner) => {
self.declare_list(interface, ty.name.as_deref(), inner)?
}
TypeDefKind::Type(inner) => match ty.name.as_deref() {
Some(name) => {
write!(&mut self.output, "type {} = ", name)?;
self.print_type_name(interface, inner)?;
self.output.push_str("\n\n");
}
None => bail!("unnamed type in interface"),
},
TypeDefKind::Future(_) => todo!("declare future"),
TypeDefKind::Stream(_) => todo!("declare stream"),
}
}
Type::Handle(_) => bail!("interface has unsupported type"),
}
Ok(())
}
fn declare_record(
&mut self,
interface: &Interface,
name: Option<&str>,
record: &Record,
) -> Result<()> {
for field in record.fields.iter() {
self.declare_type(interface, &field.ty)?;
}
match name {
Some(name) => {
writeln!(&mut self.output, "record {} {{", name)?;
for field in &record.fields {
write!(&mut self.output, " {}: ", field.name)?;
self.declare_type(interface, &field.ty)?;
self.print_type_name(interface, &field.ty)?;
self.output.push_str(",\n");
}
self.output.push_str("}\n\n");
Ok(())
}
None => bail!("interface has unnamed record type"),
}
}
fn declare_tuple(
&mut self,
interface: &Interface,
name: Option<&str>,
tuple: &Tuple,
) -> Result<()> {
for ty in tuple.types.iter() {
self.declare_type(interface, ty)?;
}
if let Some(name) = name {
write!(&mut self.output, "type {} = ", name)?;
self.print_tuple_type(interface, tuple)?;
self.output.push_str("\n\n");
}
Ok(())
}
fn declare_flags(&mut self, name: Option<&str>, flags: &Flags) -> Result<()> {
match name {
Some(name) => {
writeln!(&mut self.output, "flags {} {{", name)?;
for flag in &flags.flags {
writeln!(&mut self.output, " {},", flag.name)?;
}
self.output.push_str("}\n\n");
}
None => bail!("interface has unnamed flags type"),
}
Ok(())
}
fn declare_variant(
&mut self,
interface: &Interface,
name: Option<&str>,
variant: &Variant,
) -> Result<()> {
for case in variant.cases.iter() {
self.declare_type(interface, &case.ty)?;
}
let name = match name {
Some(name) => name,
None => bail!("interface has unnamed union type"),
};
writeln!(&mut self.output, "variant {} {{", name)?;
for case in &variant.cases {
write!(&mut self.output, " {}", case.name)?;
if case.ty != Type::Unit {
self.output.push('(');
self.print_type_name(interface, &case.ty)?;
self.output.push(')');
}
self.output.push_str(",\n");
}
self.output.push_str("}\n\n");
Ok(())
}
fn declare_union(
&mut self,
interface: &Interface,
name: Option<&str>,
union: &Union,
) -> Result<()> {
for case in union.cases.iter() {
self.declare_type(interface, &case.ty)?;
}
let name = match name {
Some(name) => name,
None => bail!("interface has unnamed union type"),
};
writeln!(&mut self.output, "union {} {{", name)?;
for case in &union.cases {
self.output.push_str(" ");
self.print_type_name(interface, &case.ty)?;
self.output.push_str(",\n");
}
self.output.push_str("}\n\n");
Ok(())
}
fn declare_option(
&mut self,
interface: &Interface,
name: Option<&str>,
payload: &Type,
) -> Result<()> {
self.declare_type(interface, payload)?;
if let Some(name) = name {
write!(&mut self.output, "type {} = ", name)?;
self.print_option_type(interface, payload)?;
self.output.push_str("\n\n");
}
Ok(())
}
fn declare_expected(
&mut self,
interface: &Interface,
name: Option<&str>,
expected: &Expected,
) -> Result<()> {
self.declare_type(interface, &expected.ok)?;
self.declare_type(interface, &expected.err)?;
if let Some(name) = name {
write!(&mut self.output, "type {} = ", name)?;
self.print_expected_type(interface, expected)?;
self.output.push_str("\n\n");
}
Ok(())
}
fn declare_enum(&mut self, name: Option<&str>, enum_: &Enum) -> Result<()> {
let name = match name {
Some(name) => name,
None => bail!("interface has unnamed enum type"),
};
writeln!(&mut self.output, "enum {} {{", name)?;
for case in &enum_.cases {
writeln!(&mut self.output, " {},", case.name)?;
}
self.output.push_str("}\n\n");
Ok(())
}
fn declare_list(&mut self, interface: &Interface, name: Option<&str>, ty: &Type) -> Result<()> {
self.declare_type(interface, ty)?;
if let Some(name) = name {
write!(&mut self.output, "type {} = list<", name)?;
self.print_type_name(interface, ty)?;
self.output.push_str(">\n\n");
return Ok(());
}
Ok(())
}
}

View File

@@ -0,0 +1,254 @@
use anyhow::{anyhow, bail, Result};
use indexmap::{map::Entry, IndexMap, IndexSet};
use std::borrow::Cow;
use wasmparser::{
types::Types, Encoding, ExternalKind, FuncType, Parser, Payload, TypeRef, ValType,
ValidPayload, Validator,
};
use wit_parser::{
abi::{AbiVariant, WasmSignature, WasmType},
Interface,
};
fn is_wasi(name: &str) -> bool {
name == "wasi_unstable" || name == "wasi_snapshot_preview1"
}
fn is_canonical_function(name: &str) -> bool {
name.starts_with("canonical_abi_")
}
pub fn expected_export_name<'a>(interface: Option<&str>, func: &'a str) -> Cow<'a, str> {
// TODO: wit-bindgen currently doesn't mangle its export names, so this
// only works with the default (i.e. `None`) interface.
match interface {
Some(interface) => format!("{}#{}", interface, func).into(),
None => func.into(),
}
}
fn wasm_sig_to_func_type(signature: WasmSignature) -> FuncType {
fn from_wasm_type(ty: &WasmType) -> ValType {
match ty {
WasmType::I32 => ValType::I32,
WasmType::I64 => ValType::I64,
WasmType::F32 => ValType::F32,
WasmType::F64 => ValType::F64,
}
}
FuncType {
params: signature
.params
.iter()
.map(from_wasm_type)
.collect::<Vec<_>>()
.into_boxed_slice(),
returns: signature
.results
.iter()
.map(from_wasm_type)
.collect::<Vec<_>>()
.into_boxed_slice(),
}
}
/// This function validates the following:
/// * The bytes represent a core WebAssembly module.
/// * The module's imports are all satisfied by the given import interfaces.
/// * The given default and exported interfaces are satisfied by the module's exports.
///
/// Returns a tuple of the set of imported interfaces required by the module, whether
/// the module exports a memory, and whether the module exports a realloc function.
pub fn validate_module<'a>(
bytes: &'a [u8],
interface: &Option<&Interface>,
imports: &[Interface],
exports: &[Interface],
) -> Result<(IndexSet<&'a str>, bool, bool)> {
let imports: IndexMap<&str, &Interface> =
imports.iter().map(|i| (i.name.as_str(), i)).collect();
let exports: IndexMap<&str, &Interface> =
exports.iter().map(|i| (i.name.as_str(), i)).collect();
let mut validator = Validator::new();
let mut types = None;
let mut import_funcs = IndexMap::new();
let mut export_funcs = IndexMap::new();
let mut has_memory = false;
let mut has_realloc = false;
for payload in Parser::new(0).parse_all(bytes) {
let payload = payload?;
if let ValidPayload::End(tys) = validator.payload(&payload)? {
types = Some(tys);
break;
}
match payload {
Payload::Version { encoding, .. } if encoding != Encoding::Module => {
bail!("data is not a WebAssembly module");
}
Payload::ImportSection(s) => {
for import in s {
let import = import?;
if is_wasi(import.module) {
continue;
}
match import.ty {
TypeRef::Func(ty) => {
let map = match import_funcs.entry(import.module) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => e.insert(IndexMap::new()),
};
assert!(map.insert(import.name, ty).is_none());
}
_ => bail!("module is only allowed to import functions"),
}
}
}
Payload::ExportSection(s) => {
for export in s {
let export = export?;
match export.kind {
ExternalKind::Func => {
if is_canonical_function(export.name) {
if export.name == "canonical_abi_realloc" {
// TODO: validate that the canonical_abi_realloc function is [i32, i32, i32, i32] -> [i32]
has_realloc = true;
}
continue;
}
assert!(export_funcs.insert(export.name, export.index).is_none())
}
ExternalKind::Memory => {
if export.name == "memory" {
has_memory = true;
}
}
_ => continue,
}
}
}
_ => continue,
}
}
let types = types.unwrap();
for (name, funcs) in &import_funcs {
if name.is_empty() {
bail!("module imports from an empty module name");
}
match imports.get(name) {
Some(interface) => {
validate_imported_interface(interface, name, funcs, &types)?;
}
None => bail!("module requires an import interface named `{}`", name),
}
}
if let Some(interface) = interface {
validate_exported_interface(interface, None, &export_funcs, &types)?;
}
for (name, interface) in exports {
if name.is_empty() {
bail!("cannot export an interface with an empty name");
}
validate_exported_interface(interface, Some(name), &export_funcs, &types)?;
}
Ok((
import_funcs.keys().cloned().collect(),
has_memory,
has_realloc,
))
}
fn validate_imported_interface(
interface: &Interface,
name: &str,
imports: &IndexMap<&str, u32>,
types: &Types,
) -> Result<()> {
for (func_name, ty) in imports {
let f = interface
.functions
.iter()
.find(|f| f.name == *func_name)
.ok_or_else(|| {
anyhow!(
"import interface `{}` is missing function `{}` that is required by the module",
name,
func_name,
)
})?;
let expected = wasm_sig_to_func_type(interface.wasm_signature(AbiVariant::GuestImport, f));
let ty = types.func_type_at(*ty).unwrap();
if ty != &expected {
bail!(
"type mismatch for function `{}` on imported interface `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`",
func_name,
name,
expected.params,
expected.returns,
ty.params,
ty.returns
);
}
}
Ok(())
}
fn validate_exported_interface(
interface: &Interface,
name: Option<&str>,
exports: &IndexMap<&str, u32>,
types: &Types,
) -> Result<()> {
for f in &interface.functions {
let expected_export = expected_export_name(name, &f.name);
match exports.get(expected_export.as_ref()) {
Some(func_index) => {
let expected_ty =
wasm_sig_to_func_type(interface.wasm_signature(AbiVariant::GuestExport, f));
let ty = types.function_at(*func_index).unwrap();
if ty != &expected_ty {
match name {
Some(name) => bail!(
"type mismatch for function `{}` from exported interface `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`",
f.name,
name,
expected_ty.params,
expected_ty.returns,
ty.params,
ty.returns
),
None => bail!(
"type mismatch for default interface function `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`",
f.name,
expected_ty.params,
expected_ty.returns,
ty.params,
ty.returns
)
}
}
}
None => bail!(
"module does not export required function `{}`",
expected_export
),
}
}
Ok(())
}