use crate::analysis::{
AnalysedExport, AnalysedFunction, AnalysedFunctionParameter, AnalysedFunctionResult,
AnalysedInstance, AnalysedResourceId, AnalysedResourceMode, AnalysedType, AnalysisFailure,
AnalysisResult, AnalysisWarning, InterfaceCouldNotBeAnalyzedWarning, NameOptionTypePair,
NameTypePair, TypeHandle, analysed_type,
};
use crate::metadata::Producers;
use itertools::Itertools;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
use std::path::Path;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use wasm_metadata::{Payload, Version};
use wasmparser::KnownCustom;
use wit_parser::decoding::DecodedWasm;
use wit_parser::{
Function, Handle, Interface, PackageName, Resolve, Type, TypeDef, TypeDefKind, WorldItem,
};
use wit_parser::{TypeId, TypeOwner as WitParserTypeOwner};
pub struct WitAnalysisContext {
wasm: DecodedWasm,
metadata_name: Option<String>,
metadata_version: Option<Version>,
resource_ids: Rc<RefCell<HashMap<TypeId, AnalysedResourceId>>>,
warnings: Rc<RefCell<Vec<AnalysisWarning>>>,
linear_memories: Vec<wasmparser::MemoryType>,
producers: Vec<Producers>,
}
impl WitAnalysisContext {
pub fn new(component_bytes: &[u8]) -> AnalysisResult<WitAnalysisContext> {
let wasm = wit_parser::decoding::decode(component_bytes).map_err(|err| {
AnalysisFailure::failed(format!("Failed to decode WASM component: {err:#}"))
})?;
let payload = Payload::from_binary(component_bytes).map_err(|err| {
AnalysisFailure::failed(format!("Failed to decode WASM component metadata: {err:#}"))
})?;
let metadata = payload.metadata();
let (linear_memories, producers) = Self::extract_additional_information(component_bytes)?;
Ok(Self {
wasm,
metadata_name: metadata.name.clone(),
metadata_version: metadata.version.clone(),
resource_ids: Rc::new(RefCell::new(HashMap::new())),
warnings: Rc::new(RefCell::new(Vec::new())),
linear_memories,
producers,
})
}
pub fn get_top_level_exports(&self) -> AnalysisResult<Vec<AnalysedExport>> {
let package_id = self.wasm.package();
let resolve = self.wasm.resolve();
let root_package =
AnalysisFailure::fail_on_missing(resolve.packages.get(package_id), "root package")?;
if root_package.worlds.len() > 1 {
Err(AnalysisFailure::failed(
"The component's root package must contains a single world",
))
} else if root_package.worlds.is_empty() {
Err(AnalysisFailure::failed(
"The component's root package must contain a world",
))
} else {
let (_world_name, world_id) = root_package.worlds.iter().next().unwrap();
let world = AnalysisFailure::fail_on_missing(resolve.worlds.get(*world_id), "world")?;
let mut result = Vec::new();
for (world_key, world_item) in &world.exports {
let world_name = resolve.name_world_key(world_key);
match world_item {
WorldItem::Interface { id, .. } => {
let interface = AnalysisFailure::fail_on_missing(
resolve.interfaces.get(*id),
"interface",
)?;
match self.analyse_interface(interface) {
Ok(instance) => result.push(AnalysedExport::Instance(instance)),
Err(failure) => {
self.warning(AnalysisWarning::InterfaceCouldNotBeAnalyzed(
InterfaceCouldNotBeAnalyzedWarning {
name: world_name,
failure,
},
))
}
}
}
WorldItem::Function(function) => {
result.push(AnalysedExport::Function(self.analyse_function(function)?));
}
WorldItem::Type(_) => {}
}
}
Ok(result)
}
}
pub fn linear_memories(&self) -> &[wasmparser::MemoryType] {
&self.linear_memories
}
pub fn producers(&self) -> &[Producers] {
&self.producers
}
pub fn root_package_name(&self) -> Option<PackageName> {
self.metadata_name.as_ref().and_then(|name| {
let parts = name.split(':').collect::<Vec<_>>();
if parts.len() == 2 {
let namespace = parts[0].to_string();
let name = parts[1].to_string();
let version = self
.metadata_version
.as_ref()
.and_then(|version| semver::Version::from_str(&version.to_string()).ok());
Some(PackageName {
namespace,
name,
version,
})
} else {
None
}
})
}
pub fn serialized_interface_only(&self) -> AnalysisResult<Vec<u8>> {
let decoded_package = self.wasm.package();
let bytes = wit_component::encode(self.wasm.resolve(), decoded_package).map_err(|err| {
AnalysisFailure::failed(format!(
"Failed to encode WASM component interface: {err:#}"
))
})?;
Ok(bytes)
}
pub fn warnings(&self) -> Vec<AnalysisWarning> {
self.warnings.borrow().clone()
}
fn warning(&self, warning: AnalysisWarning) {
self.warnings.borrow_mut().push(warning);
}
fn analyse_function(&self, function: &Function) -> AnalysisResult<AnalysedFunction> {
Ok(AnalysedFunction {
name: function.name.clone(),
parameters: function
.params
.iter()
.map(|(name, typ)| {
typ.to_analysed_type(self.wasm.resolve(), self)
.map_err(|err| {
AnalysisFailure::failed(format!(
"Failed to decode function ({}) parameter ({}) type: {}",
function.name, name, err
))
})
.map(|typ| AnalysedFunctionParameter {
name: name.clone(),
typ,
})
})
.collect::<Result<Vec<_>, _>>()?,
result: function
.result
.map(|typ| {
typ.to_analysed_type(self.wasm.resolve(), self)
.map_err(|err| {
AnalysisFailure::failed(format!(
"Failed to decode function ({}) result type: {}",
function.name, err
))
})
.map(|typ| AnalysedFunctionResult { typ })
})
.transpose()?,
})
}
fn analyse_interface(&self, interface: &Interface) -> AnalysisResult<AnalysedInstance> {
let mut functions = Vec::new();
for (_, function) in &interface.functions {
functions.push(self.analyse_function(function)?);
}
let interface_name =
AnalysisFailure::fail_on_missing(interface.name.clone(), "interface name")?;
let package_id = AnalysisFailure::fail_on_missing(interface.package, "interface package")?;
let package = AnalysisFailure::fail_on_missing(
self.wasm.resolve().packages.get(package_id),
"interface package",
)?;
Ok(AnalysedInstance {
name: package.name.interface_id(&interface_name),
functions,
})
}
fn extract_additional_information(
component_bytes: &[u8],
) -> AnalysisResult<(Vec<wasmparser::MemoryType>, Vec<Producers>)> {
let mut memories = Vec::new();
let mut producers = Vec::new();
for payload in wasmparser::Parser::new(0).parse_all(component_bytes) {
let payload = payload.map_err(|err| AnalysisFailure::failed(err.to_string()))?;
match payload {
wasmparser::Payload::MemorySection(reader) => {
for memory_type in reader {
memories.push(
memory_type.map_err(|err| AnalysisFailure::failed(err.to_string()))?,
);
}
}
wasmparser::Payload::CustomSection(reader) => {
if let KnownCustom::Producers(_) = reader.as_known() {
let parsed_producers = wasm_metadata::Producers::from_bytes(
reader.data(),
reader.data_offset(),
)
.map_err(|err| AnalysisFailure::failed(err.to_string()))?;
producers.push(parsed_producers.into());
}
}
_ => {}
}
}
Ok((memories, producers))
}
}
impl GetResourceId for WitAnalysisContext {
fn get_resource_id(&self, type_id: TypeId) -> Option<AnalysedResourceId> {
let new_unique_id = self.resource_ids.borrow().len() as u64;
let mut resource_ids = self.resource_ids.borrow_mut();
Some(
*resource_ids
.entry(type_id)
.or_insert_with(|| AnalysedResourceId(new_unique_id)),
)
}
}
pub trait GetResourceId {
fn get_resource_id(&self, type_id: TypeId) -> Option<AnalysedResourceId>;
}
pub struct ResourcesNotSupported;
impl GetResourceId for ResourcesNotSupported {
fn get_resource_id(&self, _type_id: TypeId) -> Option<AnalysedResourceId> {
None
}
}
pub trait ToAnalysedType {
fn to_analysed_type(
&self,
resolve: &Resolve,
resource_map: &impl GetResourceId,
) -> Result<AnalysedType, String>;
}
impl ToAnalysedType for TypeDef {
fn to_analysed_type(
&self,
resolve: &Resolve,
resource_map: &impl GetResourceId,
) -> Result<AnalysedType, String> {
match &self.kind {
TypeDefKind::Record(record) => Ok(analysed_type::record(
record
.fields
.iter()
.map(|field| {
field
.ty
.to_analysed_type(resolve, resource_map)
.map(|typ| NameTypePair {
name: field.name.clone(),
typ,
})
})
.collect::<Result<_, _>>()?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::Resource => {
Err("to_analysed_type not implemented for resource type".to_string())
}
TypeDefKind::Handle(handle) => match handle {
Handle::Own(type_id) => match resource_map.get_resource_id(*type_id) {
Some(resource_id) => Ok(AnalysedType::Handle(TypeHandle {
resource_id,
mode: AnalysedResourceMode::Owned,
name: self.name.clone(),
owner: None,
})
.with_optional_name(get_type_name(resolve, type_id))
.with_optional_owner(get_type_owner(resolve, type_id))),
None => Err("to_analysed_type not implemented for handle type".to_string()),
},
Handle::Borrow(type_id) => match resource_map.get_resource_id(*type_id) {
Some(resource_id) => Ok(AnalysedType::Handle(TypeHandle {
resource_id,
mode: AnalysedResourceMode::Borrowed,
name: self.name.clone(),
owner: None,
})
.with_optional_name(get_type_name(resolve, type_id))
.with_optional_owner(get_type_owner(resolve, type_id))),
None => Err("to_analysed_type not implemented for handle type".to_string()),
},
},
TypeDefKind::Flags(flag) => Ok(analysed_type::flags(
&flag
.flags
.iter()
.map(|flag| flag.name.as_str())
.collect::<Vec<_>>(),
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::Tuple(tuple) => Ok(analysed_type::tuple(
tuple
.types
.iter()
.map(|typ| typ.to_analysed_type(resolve, resource_map))
.collect::<Result<_, _>>()?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::Variant(variant) => Ok(analysed_type::variant(
variant
.cases
.iter()
.map(|case| {
case.ty
.map(|ty| ty.to_analysed_type(resolve, resource_map))
.transpose()
.map(|ty| NameOptionTypePair {
name: case.name.clone(),
typ: ty,
})
})
.collect::<Result<_, _>>()?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::Enum(enum_) => Ok(analysed_type::r#enum(
&enum_
.cases
.iter()
.map(|case| case.name.as_str())
.collect::<Vec<_>>(),
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::Option(inner) => Ok(analysed_type::option(
inner.to_analysed_type(resolve, resource_map)?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::Result(result) => match (result.ok, result.err) {
(Some(ok), Some(err)) => Ok(analysed_type::result(
ok.to_analysed_type(resolve, resource_map)?,
err.to_analysed_type(resolve, resource_map)?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
(Some(ok), None) => Ok(analysed_type::result_ok(
ok.to_analysed_type(resolve, resource_map)?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
(None, Some(err)) => Ok(analysed_type::result_err(
err.to_analysed_type(resolve, resource_map)?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
(None, None) => Ok(analysed_type::unit_result()
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
},
TypeDefKind::List(ty) => Ok(analysed_type::list(
ty.to_analysed_type(resolve, resource_map)?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::FixedSizeList(ty, _) => Ok(analysed_type::list(
ty.to_analysed_type(resolve, resource_map)?,
)
.with_optional_name(self.name.clone())
.with_optional_owner(get_owner_name(resolve, &self.owner))),
TypeDefKind::Future(_) => {
Err("to_analysed_type not implemented for future type".to_string())
}
TypeDefKind::Stream(_) => {
Err("to_analysed_type not implemented for stream type".to_string())
}
TypeDefKind::Map(_, _) => {
Err("to_analysed_type not implemented for map type".to_string())
}
TypeDefKind::Type(typ) => typ.to_analysed_type(resolve, resource_map),
TypeDefKind::Unknown => Err("to_analysed_type unknown type".to_string()),
}
}
}
impl ToAnalysedType for Type {
fn to_analysed_type(
&self,
resolve: &Resolve,
resource_map: &impl GetResourceId,
) -> Result<AnalysedType, String> {
match self {
Type::Bool => Ok(analysed_type::bool()),
Type::U8 => Ok(analysed_type::u8()),
Type::U16 => Ok(analysed_type::u16()),
Type::U32 => Ok(analysed_type::u32()),
Type::U64 => Ok(analysed_type::u64()),
Type::S8 => Ok(analysed_type::s8()),
Type::S16 => Ok(analysed_type::s16()),
Type::S32 => Ok(analysed_type::s32()),
Type::S64 => Ok(analysed_type::s64()),
Type::F32 => Ok(analysed_type::f32()),
Type::F64 => Ok(analysed_type::f64()),
Type::Char => Ok(analysed_type::chr()),
Type::String => Ok(analysed_type::str()),
Type::Id(id) => resolve
.types
.get(*id)
.ok_or_else(|| format!("Type not found by id: {id:?}"))?
.to_analysed_type(resolve, resource_map),
Type::ErrorContext => Err("ErrorContext not supported".to_string()),
}
}
}
fn follow_aliases(resolve: &Resolve, type_id: &TypeId) -> TypeId {
let mut current_id = *type_id;
while let Some(type_def) = resolve.types.get(current_id) {
if let TypeDefKind::Type(Type::Id(alias_type_id)) = &type_def.kind {
current_id = *alias_type_id;
} else {
break;
}
}
current_id
}
fn get_type_name(resolve: &Resolve, type_id: &TypeId) -> Option<String> {
resolve
.types
.get(follow_aliases(resolve, type_id))
.and_then(|type_def| type_def.name.clone())
}
fn get_type_owner(resolve: &Resolve, type_id: &TypeId) -> Option<String> {
resolve
.types
.get(follow_aliases(resolve, type_id))
.and_then(|type_def| get_owner_name(resolve, &type_def.owner))
}
fn get_owner_name(resolve: &Resolve, owner: &wit_parser::TypeOwner) -> Option<String> {
match owner {
wit_parser::TypeOwner::World(world_id) => resolve
.worlds
.get(*world_id)
.map(|world| world.name.clone()),
wit_parser::TypeOwner::Interface(iface_id) => resolve
.interfaces
.get(*iface_id)
.and_then(|iface| iface.name.clone().map(|name| (iface.package, name)))
.and_then(|(package_id, name)| {
if let Some(package_id) = package_id {
resolve
.packages
.get(package_id)
.map(|package| format!("{}/{}", package.name, name))
} else {
Some(name)
}
}),
wit_parser::TypeOwner::None => None,
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum TypeOwner {
World(String),
Interface(String),
InlineInterface,
None,
}
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct TypeName {
pub package: Option<String>,
pub owner: TypeOwner,
pub name: Option<String>,
}
pub struct AnalysedTypeResolve {
resolve: Resolve,
type_name_to_id: HashMap<TypeName, TypeId>,
id_to_analysed_type: HashMap<TypeId, AnalysedType>,
}
impl Debug for AnalysedTypeResolve {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AnalysedTypeResolve")
}
}
impl AnalysedTypeResolve {
pub fn new(resolve: Resolve) -> Self {
let type_name_to_id = resolve
.types
.iter()
.map(|(type_id, type_def)| {
(
match &type_def.owner {
WitParserTypeOwner::World(world_id) => {
let world = resolve.worlds.get(*world_id).unwrap();
TypeName {
package: world
.package
.and_then(|package_id| resolve.packages.get(package_id))
.map(|package| package.name.to_string()),
owner: TypeOwner::World(world.name.clone()),
name: type_def.name.clone(),
}
}
WitParserTypeOwner::Interface(interface_id) => {
let interface = resolve.interfaces.get(*interface_id).unwrap();
TypeName {
package: interface
.package
.and_then(|package_id| resolve.packages.get(package_id))
.map(|package| package.name.to_string()),
owner: {
match &interface.name {
Some(name) => TypeOwner::Interface(name.clone()),
None => TypeOwner::InlineInterface,
}
},
name: type_def.name.clone(),
}
}
WitParserTypeOwner::None => TypeName {
package: None,
owner: TypeOwner::None,
name: type_def.name.clone(),
},
},
type_id,
)
})
.collect::<HashMap<_, _>>();
AnalysedTypeResolve {
resolve,
type_name_to_id,
id_to_analysed_type: HashMap::new(),
}
}
pub fn from_wit_directory(directory: &Path) -> Result<Self, String> {
let mut resolve = Resolve::new();
resolve.push_dir(directory).map_err(|e| e.to_string())?;
Ok(Self::new(resolve))
}
pub fn from_wit_str(wit: &str) -> Result<Self, String> {
let mut resolve = Resolve::new();
resolve
.push_str(wit, "wit.wit")
.map_err(|e| e.to_string())?;
Ok(Self::new(resolve))
}
pub fn analysed_type(&mut self, name: &TypeName) -> Result<AnalysedType, String> {
match self.type_name_to_id.get(name) {
Some(type_id) => match self.id_to_analysed_type.get(type_id) {
Some(typ) => Ok(typ.clone()),
None => {
let typ = self
.resolve
.types
.get(*type_id)
.unwrap()
.to_analysed_type(&self.resolve, &ResourcesNotSupported)?;
self.id_to_analysed_type.insert(*type_id, typ.clone());
Ok(typ)
}
},
None => Err(format!(
"Type not found by name: {:?}, available types: {}",
name,
{
self.type_name_to_id
.keys()
.map(|type_id| format!("{type_id:?}"))
.join("\n")
}
)),
}
}
}
#[derive(Clone, Debug)]
pub struct SharedAnalysedTypeResolve {
resolve: Arc<Mutex<AnalysedTypeResolve>>,
}
impl SharedAnalysedTypeResolve {
pub fn new(resolve: AnalysedTypeResolve) -> Self {
Self {
resolve: Arc::new(Mutex::new(resolve)),
}
}
pub fn analysed_type(&mut self, name: &TypeName) -> Result<AnalysedType, String> {
self.resolve.lock().unwrap().analysed_type(name)
}
}