feat: add a histrical wit-bindgen
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user