//! State relating to validating a WebAssembly component. use super::{ check_max, combine_type_sizes, core::Module, types::{ ComponentFuncType, ComponentInstanceType, ComponentInstanceTypeKind, ComponentType, ComponentValType, EntityType, InstanceType, KebabString, ModuleType, RecordType, Type, TypeAlloc, TypeId, TypeList, VariantCase, }, }; use crate::{ limits::*, types::{ ComponentDefinedType, ComponentEntityType, InstanceTypeKind, KebabStr, LoweringInfo, TupleType, UnionType, VariantType, }, BinaryReaderError, CanonicalOption, ComponentExternalKind, ComponentOuterAliasKind, ComponentTypeRef, ExternalKind, FuncType, GlobalType, InstantiationArgKind, MemoryType, Result, TableType, TypeBounds, ValType, WasmFeatures, }; use indexmap::{map::Entry, IndexMap, IndexSet}; use std::{collections::HashSet, mem}; use url::Url; fn to_kebab_str<'a>(s: &'a str, desc: &str, offset: usize) -> Result<&'a KebabStr> { match KebabStr::new(s) { Some(s) => Ok(s), None => { if s.is_empty() { bail!(offset, "{desc} name cannot be empty"); } bail!(offset, "{desc} name `{s}` is not in kebab case"); } } } fn parse_url(url: &str, offset: usize) -> Result> { if url.is_empty() { return Ok(None); } Url::parse(url) .map(Some) .map_err(|e| BinaryReaderError::new(e.to_string(), offset)) } pub(crate) struct ComponentState { // Core index spaces pub core_types: Vec, pub core_modules: Vec, pub core_instances: Vec, pub core_funcs: Vec, pub core_memories: Vec, pub core_tables: Vec, pub core_globals: Vec, pub core_tags: Vec, // Component index spaces pub types: Vec, pub funcs: Vec, pub values: Vec<(ComponentValType, bool)>, pub instances: Vec, pub components: Vec, /// A set of all imports and exports since they share the same namespace. pub externs: IndexMap, ComponentEntityType, ExternKind)>, // Note: URL validation requires unique URLs by byte comparison, so // strings are used here and the URLs are not normalized. import_urls: HashSet, export_urls: HashSet, has_start: bool, type_size: u32, } pub enum ExternKind { Import, Export, } impl ExternKind { fn desc(&self) -> &'static str { match self { ExternKind::Import => "import", ExternKind::Export => "export", } } } impl ComponentState { pub fn type_count(&self) -> usize { self.core_types.len() + self.types.len() } pub fn instance_count(&self) -> usize { self.core_instances.len() + self.instances.len() } pub fn function_count(&self) -> usize { self.core_funcs.len() + self.funcs.len() } pub fn add_core_type( components: &mut [Self], ty: crate::CoreType, features: &WasmFeatures, types: &mut TypeAlloc, offset: usize, check_limit: bool, ) -> Result<()> { let ty = match ty { crate::CoreType::Func(ty) => Type::Func(ty), crate::CoreType::Module(decls) => Type::Module(Self::create_module_type( components, decls.into_vec(), features, types, offset, )?), }; let current = components.last_mut().unwrap(); if check_limit { check_max(current.type_count(), 1, MAX_WASM_TYPES, "types", offset)?; } let id = types.push_defined(ty); current.core_types.push(id); Ok(()) } pub fn add_core_module( &mut self, module: &Module, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let imports = module.imports_for_module_type(offset)?; // We have to clone the module's imports and exports here // because we cannot take the data out of the `MaybeOwned` // as it might be shared with a function validator. let ty = Type::Module(ModuleType { type_size: module.type_size, imports, exports: module.exports.clone(), }); let id = types.push_anon(ty); self.core_modules.push(id); Ok(()) } pub fn add_core_instance( &mut self, instance: crate::Instance, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let instance = match instance { crate::Instance::Instantiate { module_index, args } => { self.instantiate_module(module_index, args.into_vec(), types, offset)? } crate::Instance::FromExports(exports) => { self.instantiate_core_exports(exports.into_vec(), types, offset)? } }; self.core_instances.push(instance); Ok(()) } pub fn add_type( components: &mut Vec, ty: crate::ComponentType, features: &WasmFeatures, types: &mut TypeAlloc, offset: usize, check_limit: bool, ) -> Result<()> { assert!(!components.is_empty()); let ty = match ty { crate::ComponentType::Defined(ty) => Type::Defined( components .last_mut() .unwrap() .create_defined_type(ty, types, offset)?, ), crate::ComponentType::Func(ty) => Type::ComponentFunc( components .last_mut() .unwrap() .create_function_type(ty, types, offset)?, ), crate::ComponentType::Component(decls) => Type::Component(Self::create_component_type( components, decls.into_vec(), features, types, offset, )?), crate::ComponentType::Instance(decls) => Type::ComponentInstance( Self::create_instance_type(components, decls.into_vec(), features, types, offset)?, ), }; let current = components.last_mut().unwrap(); if check_limit { check_max(current.type_count(), 1, MAX_WASM_TYPES, "types", offset)?; } let id = types.push_defined(ty); current.types.push(id); Ok(()) } pub fn add_import( &mut self, import: crate::ComponentImport, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let entity = self.check_type_ref(&import.ty, types, offset)?; self.add_entity(entity, false, offset)?; let name = to_kebab_str(import.name, "import", offset)?; match self.externs.entry(name.to_owned()) { Entry::Occupied(e) => { bail!( offset, "import name `{name}` conflicts with previous {desc} name `{prev}`", name = import.name, prev = e.key(), desc = e.get().2.desc(), ); } Entry::Vacant(e) => { let url = parse_url(import.url, offset)?; if let Some(url) = url.as_ref() { if !self.import_urls.insert(url.to_string()) { bail!(offset, "duplicate import URL `{url}`"); } } self.type_size = combine_type_sizes(self.type_size, entity.type_size(), offset)?; e.insert((url, entity, ExternKind::Import)); } } Ok(()) } fn add_entity( &mut self, ty: ComponentEntityType, value_used: bool, offset: usize, ) -> Result<()> { let (len, max, desc) = match ty { ComponentEntityType::Module(id) => { self.core_modules.push(id); (self.core_modules.len(), MAX_WASM_MODULES, "modules") } ComponentEntityType::Component(id) => { self.components.push(id); (self.components.len(), MAX_WASM_COMPONENTS, "components") } ComponentEntityType::Instance(id) => { self.instances.push(id); (self.instance_count(), MAX_WASM_INSTANCES, "instances") } ComponentEntityType::Func(id) => { self.funcs.push(id); (self.function_count(), MAX_WASM_FUNCTIONS, "functions") } ComponentEntityType::Value(ty) => { self.values.push((ty, value_used)); (self.values.len(), MAX_WASM_VALUES, "values") } ComponentEntityType::Type { created, .. } => { self.types.push(created); (self.types.len(), MAX_WASM_TYPES, "types") } }; check_max(len, 0, max, desc, offset)?; Ok(()) } pub fn add_export( &mut self, name: &str, url: &str, ty: ComponentEntityType, offset: usize, check_limit: bool, ) -> Result<()> { if check_limit { check_max( self.externs.len(), 1, MAX_WASM_EXPORTS, "imports and exports", offset, )?; } self.add_entity(ty, true, offset)?; let name = to_kebab_str(name, "export", offset)?; match self.externs.entry(name.to_owned()) { Entry::Occupied(e) => { bail!( offset, "export name `{name}` conflicts with previous {desc} name `{prev}`", prev = e.key(), desc = e.get().2.desc(), ); } Entry::Vacant(e) => { let url = parse_url(url, offset)?; if let Some(url) = url.as_ref() { if !self.export_urls.insert(url.to_string()) { bail!(offset, "duplicate export URL `{url}`"); } } self.type_size = combine_type_sizes(self.type_size, ty.type_size(), offset)?; e.insert((url, ty, ExternKind::Export)); } } Ok(()) } pub fn lift_function( &mut self, core_func_index: u32, type_index: u32, options: Vec, types: &TypeList, offset: usize, ) -> Result<()> { let ty = self.function_type_at(type_index, types, offset)?; let core_ty = types[self.core_function_at(core_func_index, offset)?] .as_func_type() .unwrap(); // Lifting a function is for an export, so match the expected canonical ABI // export signature let info = ty.lower(types, false); self.check_options(Some(core_ty), &info, &options, types, offset)?; if core_ty.params() != info.params.as_slice() { bail!( offset, "lowered parameter types `{:?}` do not match parameter types \ `{:?}` of core function {core_func_index}", info.params.as_slice(), core_ty.params(), ); } if core_ty.results() != info.results.as_slice() { bail!( offset, "lowered result types `{:?}` do not match result types \ `{:?}` of core function {core_func_index}", info.results.as_slice(), core_ty.results() ); } self.funcs.push(self.types[type_index as usize]); Ok(()) } pub fn lower_function( &mut self, func_index: u32, options: Vec, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let ty = types[self.function_at(func_index, offset)?] .as_component_func_type() .unwrap(); // Lowering a function is for an import, so use a function type that matches // the expected canonical ABI import signature. let info = ty.lower(types, true); self.check_options(None, &info, &options, types, offset)?; let lowered_ty = Type::Func(info.into_func_type()); let id = types.push_anon(lowered_ty); self.core_funcs.push(id); Ok(()) } pub fn add_component(&mut self, component: &mut Self, types: &mut TypeAlloc) { let ty = Type::Component(component.take_component_type()); let id = types.push_anon(ty); self.components.push(id); } pub fn add_instance( &mut self, instance: crate::ComponentInstance, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let instance = match instance { crate::ComponentInstance::Instantiate { component_index, args, } => self.instantiate_component(component_index, args.into_vec(), types, offset)?, crate::ComponentInstance::FromExports(exports) => { self.instantiate_exports(exports.into_vec(), types, offset)? } }; self.instances.push(instance); Ok(()) } pub fn add_alias( components: &mut [Self], alias: crate::ComponentAlias, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { match alias { crate::ComponentAlias::InstanceExport { instance_index, kind, name, } => components.last_mut().unwrap().alias_instance_export( instance_index, kind, name, types, offset, ), crate::ComponentAlias::CoreInstanceExport { instance_index, kind, name, } => components.last_mut().unwrap().alias_core_instance_export( instance_index, kind, name, types, offset, ), crate::ComponentAlias::Outer { kind, count, index } => match kind { ComponentOuterAliasKind::CoreModule => { Self::alias_module(components, count, index, offset) } ComponentOuterAliasKind::CoreType => { Self::alias_core_type(components, count, index, types, offset) } ComponentOuterAliasKind::Type => { Self::alias_type(components, count, index, types, offset) } ComponentOuterAliasKind::Component => { Self::alias_component(components, count, index, offset) } }, } } pub fn add_start( &mut self, func_index: u32, args: &[u32], results: u32, types: &TypeList, offset: usize, ) -> Result<()> { if self.has_start { return Err(BinaryReaderError::new( "component cannot have more than one start function", offset, )); } let ft = types[self.function_at(func_index, offset)?] .as_component_func_type() .unwrap(); if ft.params.len() != args.len() { bail!( offset, "component start function requires {} arguments but was given {}", ft.params.len(), args.len() ); } if ft.results.len() as u32 != results { bail!( offset, "component start function has a result count of {results} \ but the function type has a result count of {type_results}", type_results = ft.results.len(), ); } for (i, ((_, ty), arg)) in ft.params.iter().zip(args).enumerate() { // Ensure the value's type is a subtype of the parameter type if !ComponentValType::internal_is_subtype_of( self.value_at(*arg, offset)?, types, ty, types, ) { bail!( offset, "value type mismatch for component start function argument {i}" ); } } for (_, ty) in ft.results.iter() { self.values.push((*ty, false)); } self.has_start = true; Ok(()) } fn check_options( &self, core_ty: Option<&FuncType>, info: &LoweringInfo, options: &[CanonicalOption], types: &TypeList, offset: usize, ) -> Result<()> { fn display(option: CanonicalOption) -> &'static str { match option { CanonicalOption::UTF8 => "utf8", CanonicalOption::UTF16 => "utf16", CanonicalOption::CompactUTF16 => "latin1-utf16", CanonicalOption::Memory(_) => "memory", CanonicalOption::Realloc(_) => "realloc", CanonicalOption::PostReturn(_) => "post-return", } } let mut encoding = None; let mut memory = None; let mut realloc = None; let mut post_return = None; for option in options { match option { CanonicalOption::UTF8 | CanonicalOption::UTF16 | CanonicalOption::CompactUTF16 => { match encoding { Some(existing) => { bail!( offset, "canonical encoding option `{}` conflicts with option `{}`", display(existing), display(*option), ) } None => encoding = Some(*option), } } CanonicalOption::Memory(idx) => { memory = match memory { None => { self.memory_at(*idx, offset)?; Some(*idx) } Some(_) => { return Err(BinaryReaderError::new( "canonical option `memory` is specified more than once", offset, )) } } } CanonicalOption::Realloc(idx) => { realloc = match realloc { None => { let ty = types[self.core_function_at(*idx, offset)?] .as_func_type() .unwrap(); if ty.params() != [ValType::I32, ValType::I32, ValType::I32, ValType::I32] || ty.results() != [ValType::I32] { return Err(BinaryReaderError::new( "canonical option `realloc` uses a core function with an incorrect signature", offset, )); } Some(*idx) } Some(_) => { return Err(BinaryReaderError::new( "canonical option `realloc` is specified more than once", offset, )) } } } CanonicalOption::PostReturn(idx) => { post_return = match post_return { None => { let core_ty = core_ty.ok_or_else(|| { BinaryReaderError::new( "canonical option `post-return` cannot be specified for lowerings", offset, ) })?; let ty = types[self.core_function_at(*idx, offset)?] .as_func_type() .unwrap(); if ty.params() != core_ty.results() || !ty.results().is_empty() { return Err(BinaryReaderError::new( "canonical option `post-return` uses a core function with an incorrect signature", offset, )); } Some(*idx) } Some(_) => { return Err(BinaryReaderError::new( "canonical option `post-return` is specified more than once", offset, )) } } } } } if info.requires_memory && memory.is_none() { return Err(BinaryReaderError::new( "canonical option `memory` is required", offset, )); } if info.requires_realloc && realloc.is_none() { return Err(BinaryReaderError::new( "canonical option `realloc` is required", offset, )); } Ok(()) } fn check_type_ref( &self, ty: &ComponentTypeRef, types: &mut TypeAlloc, offset: usize, ) -> Result { Ok(match ty { ComponentTypeRef::Module(index) => { let id = self.type_at(*index, true, offset)?; types[id].as_module_type().ok_or_else(|| { format_err!(offset, "core type index {index} is not a module type") })?; ComponentEntityType::Module(id) } ComponentTypeRef::Func(index) => { let id = self.type_at(*index, false, offset)?; types[id].as_component_func_type().ok_or_else(|| { format_err!(offset, "type index {index} is not a function type") })?; ComponentEntityType::Func(id) } ComponentTypeRef::Value(ty) => { let ty = match ty { crate::ComponentValType::Primitive(ty) => ComponentValType::Primitive(*ty), crate::ComponentValType::Type(index) => { ComponentValType::Type(self.defined_type_at(*index, types, offset)?) } }; ComponentEntityType::Value(ty) } ComponentTypeRef::Type(TypeBounds::Eq, index) => { let referenced = self.type_at(*index, false, offset)?; let created = types.with_unique(referenced); ComponentEntityType::Type { referenced, created, } } ComponentTypeRef::Instance(index) => { let id = self.type_at(*index, false, offset)?; types[id].as_component_instance_type().ok_or_else(|| { format_err!(offset, "type index {index} is not an instance type") })?; ComponentEntityType::Instance(id) } ComponentTypeRef::Component(index) => { let id = self.type_at(*index, false, offset)?; types[id].as_component_type().ok_or_else(|| { format_err!(offset, "type index {index} is not a component type") })?; ComponentEntityType::Component(id) } }) } pub fn export_to_entity_type( &mut self, export: &crate::ComponentExport, types: &mut TypeAlloc, offset: usize, ) -> Result { let actual = match export.kind { ComponentExternalKind::Module => { ComponentEntityType::Module(self.module_at(export.index, offset)?) } ComponentExternalKind::Func => { ComponentEntityType::Func(self.function_at(export.index, offset)?) } ComponentExternalKind::Value => { ComponentEntityType::Value(*self.value_at(export.index, offset)?) } ComponentExternalKind::Type => { let referenced = self.type_at(export.index, false, offset)?; let created = types.with_unique(referenced); ComponentEntityType::Type { referenced, created, } } ComponentExternalKind::Instance => { ComponentEntityType::Instance(self.instance_at(export.index, offset)?) } ComponentExternalKind::Component => { ComponentEntityType::Component(self.component_at(export.index, offset)?) } }; let ascribed = match &export.ty { Some(ty) => self.check_type_ref(ty, types, offset)?, None => return Ok(actual), }; if !ComponentEntityType::internal_is_subtype_of(&actual, types, &ascribed, types) { bail!( offset, "ascribed type of export is not compatible with item's type" ); } Ok(ascribed) } fn create_module_type( components: &[Self], decls: Vec, features: &WasmFeatures, types: &mut TypeAlloc, offset: usize, ) -> Result { let mut state = Module::default(); for decl in decls { match decl { crate::ModuleTypeDeclaration::Type(ty) => { state.add_type(ty, features, types, offset, true)?; } crate::ModuleTypeDeclaration::Export { name, ty } => { let ty = state.check_type_ref(&ty, features, types, offset)?; state.add_export(name, ty, features, offset, true)?; } crate::ModuleTypeDeclaration::OuterAlias { kind, count, index } => { if count > 1 { return Err(BinaryReaderError::new( "outer type aliases in module type declarations are limited to a maximum count of 1", offset, )); } match kind { crate::OuterAliasKind::Type => { let ty = if count == 0 { // Local alias, check the local module state state.type_at(index, offset)? } else { // Otherwise, check the enclosing component state let component = Self::check_alias_count(components, count - 1, offset)?; component.type_at(index, true, offset)? }; check_max(state.types.len(), 1, MAX_WASM_TYPES, "types", offset)?; state.types.push(ty); } } } crate::ModuleTypeDeclaration::Import(import) => { state.add_import(import, features, types, offset)?; } } } let imports = state.imports_for_module_type(offset)?; Ok(ModuleType { type_size: state.type_size, imports, exports: state.exports, }) } fn create_component_type( components: &mut Vec, decls: Vec, features: &WasmFeatures, types: &mut TypeAlloc, offset: usize, ) -> Result { components.push(ComponentState::default()); for decl in decls { match decl { crate::ComponentTypeDeclaration::CoreType(ty) => { Self::add_core_type(components, ty, features, types, offset, true)?; } crate::ComponentTypeDeclaration::Type(ty) => { Self::add_type(components, ty, features, types, offset, true)?; } crate::ComponentTypeDeclaration::Export { name, url, ty } => { let current = components.last_mut().unwrap(); let ty = current.check_type_ref(&ty, types, offset)?; current.add_export(name, url, ty, offset, true)?; } crate::ComponentTypeDeclaration::Import(import) => { components .last_mut() .unwrap() .add_import(import, types, offset)?; } crate::ComponentTypeDeclaration::Alias(alias) => { Self::add_alias(components, alias, types, offset)?; } }; } let mut state = components.pop().unwrap(); Ok(state.take_component_type()) } fn create_instance_type( components: &mut Vec, decls: Vec, features: &WasmFeatures, types: &mut TypeAlloc, offset: usize, ) -> Result { components.push(ComponentState::default()); for decl in decls { match decl { crate::InstanceTypeDeclaration::CoreType(ty) => { Self::add_core_type(components, ty, features, types, offset, true)?; } crate::InstanceTypeDeclaration::Type(ty) => { Self::add_type(components, ty, features, types, offset, true)?; } crate::InstanceTypeDeclaration::Export { name, url, ty } => { let current = components.last_mut().unwrap(); let ty = current.check_type_ref(&ty, types, offset)?; current.add_export(name, url, ty, offset, true)?; } crate::InstanceTypeDeclaration::Alias(alias) => { Self::add_alias(components, alias, types, offset)?; } }; } let state = components.pop().unwrap(); Ok(ComponentInstanceType { type_size: state.type_size, kind: ComponentInstanceTypeKind::Defined( state .externs .into_iter() .filter_map(|(name, (url, ty, kind))| match kind { ExternKind::Export => Some((name, (url, ty))), ExternKind::Import => None, }) .collect(), ), }) } fn create_function_type( &self, ty: crate::ComponentFuncType, types: &TypeList, offset: usize, ) -> Result { let mut type_size = 1; let mut set = HashSet::with_capacity(std::cmp::max(ty.params.len(), ty.results.type_count())); let params = ty .params .iter() .map(|(name, ty)| { let name = to_kebab_str(name, "function parameter", offset)?; if !set.insert(name) { bail!( offset, "function parameter name `{name}` conflicts with previous parameter name `{prev}`", prev = set.get(&name).unwrap(), ); } let ty = self.create_component_val_type(*ty, types, offset)?; type_size = combine_type_sizes(type_size, ty.type_size(), offset)?; Ok((name.to_owned(), ty)) }) .collect::>()?; set.clear(); let results = ty .results .iter() .map(|(name, ty)| { let name = name .map(|name| { let name = to_kebab_str(name, "function result", offset)?; if !set.insert(name) { bail!( offset, "function result name `{name}` conflicts with previous result name `{prev}`", prev = set.get(name).unwrap(), ); } Ok(name.to_owned()) }) .transpose()?; let ty = self.create_component_val_type(*ty, types, offset)?; type_size = combine_type_sizes(type_size, ty.type_size(), offset)?; Ok((name, ty)) }) .collect::>()?; Ok(ComponentFuncType { type_size, params, results, }) } fn instantiate_module( &self, module_index: u32, module_args: Vec, types: &mut TypeAlloc, offset: usize, ) -> Result { fn insert_arg<'a>( name: &'a str, arg: &'a InstanceType, args: &mut IndexMap<&'a str, &'a InstanceType>, offset: usize, ) -> Result<()> { if args.insert(name, arg).is_some() { bail!( offset, "duplicate module instantiation argument named `{name}`" ); } Ok(()) } let module_type_id = self.module_at(module_index, offset)?; let mut args = IndexMap::new(); // Populate the arguments for module_arg in module_args { match module_arg.kind { InstantiationArgKind::Instance => { let instance_type = types[self.core_instance_at(module_arg.index, offset)?] .as_instance_type() .unwrap(); insert_arg(module_arg.name, instance_type, &mut args, offset)?; } } } // Validate the arguments let module_type = types[module_type_id].as_module_type().unwrap(); for ((module, name), expected) in module_type.imports.iter() { let instance = args.get(module.as_str()).ok_or_else(|| { format_err!( offset, "missing module instantiation argument named `{module}`" ) })?; let arg = instance .internal_exports(types) .get(name.as_str()) .ok_or_else(|| { format_err!( offset, "module instantiation argument `{module}` does not \ export an item named `{name}`", ) })?; match (arg, expected) { (EntityType::Func(_), EntityType::Func(_)) | (EntityType::Table(_), EntityType::Table(_)) | (EntityType::Memory(_), EntityType::Memory(_)) | (EntityType::Global(_), EntityType::Global(_)) | (EntityType::Tag(_), EntityType::Tag(_)) => {} _ => { bail!( offset, "module instantiation argument `{module}` exports \ an item named `{name}` but it is not a {}", expected.desc() ) } } if !EntityType::internal_is_subtype_of(arg, types, expected, types) { bail!( offset, "{} type mismatch for export `{name}` of module \ instantiation argument `{module}`", expected.desc(), ); } } let ty = Type::Instance(InstanceType { type_size: module_type .exports .iter() .fold(1, |acc, (_, ty)| acc + ty.type_size()), kind: InstanceTypeKind::Instantiated(module_type_id), }); Ok(types.push_anon(ty)) } fn instantiate_component( &mut self, component_index: u32, component_args: Vec, types: &mut TypeAlloc, offset: usize, ) -> Result { fn insert_arg<'a>( name: &'a str, arg: ComponentEntityType, args: &mut IndexMap<&'a KebabStr, ComponentEntityType>, offset: usize, ) -> Result<()> { let name = to_kebab_str(name, "instantiation argument", offset)?; match args.entry(name) { Entry::Occupied(e) => { bail!( offset, "instantiation argument `{name}` conflicts with previous argument `{prev}`", prev = e.key() ); } Entry::Vacant(e) => { e.insert(arg); } } Ok(()) } let component_type_id = self.component_at(component_index, offset)?; let mut args = IndexMap::new(); // Populate the arguments for component_arg in component_args { match component_arg.kind { ComponentExternalKind::Module => { insert_arg( component_arg.name, ComponentEntityType::Module(self.module_at(component_arg.index, offset)?), &mut args, offset, )?; } ComponentExternalKind::Component => { insert_arg( component_arg.name, ComponentEntityType::Component( self.component_at(component_arg.index, offset)?, ), &mut args, offset, )?; } ComponentExternalKind::Instance => { insert_arg( component_arg.name, ComponentEntityType::Instance( self.instance_at(component_arg.index, offset)?, ), &mut args, offset, )?; } ComponentExternalKind::Func => { insert_arg( component_arg.name, ComponentEntityType::Func(self.function_at(component_arg.index, offset)?), &mut args, offset, )?; } ComponentExternalKind::Value => { insert_arg( component_arg.name, ComponentEntityType::Value(*self.value_at(component_arg.index, offset)?), &mut args, offset, )?; } ComponentExternalKind::Type => { let ty = self.type_at(component_arg.index, false, offset)?; insert_arg( component_arg.name, ComponentEntityType::Type { referenced: ty, created: ty, }, &mut args, offset, )?; } } } // Validate the arguments let component_type = types[component_type_id].as_component_type().unwrap(); for (name, (_, expected)) in component_type.imports.iter() { match args.get(&name.as_kebab_str()) { Some(arg) => { match (arg, expected) { (ComponentEntityType::Module(_), ComponentEntityType::Module(_)) | (ComponentEntityType::Component(_), ComponentEntityType::Component(_)) | (ComponentEntityType::Instance(_), ComponentEntityType::Instance(_)) | (ComponentEntityType::Func(_), ComponentEntityType::Func(_)) | (ComponentEntityType::Value(_), ComponentEntityType::Value(_)) | (ComponentEntityType::Type { .. }, ComponentEntityType::Type { .. }) => {} _ => { bail!( offset, "expected component instantiation argument `{name}` to be a {desc}", desc = expected.desc() ) } }; if !ComponentEntityType::internal_is_subtype_of(arg, types, expected, types) { bail!( offset, "type mismatch for component instantiation argument `{name}`" ); } } None => { bail!( offset, "missing component instantiation argument named `{name}`" ); } } } let ty = Type::ComponentInstance(ComponentInstanceType { type_size: component_type .exports .iter() .fold(1, |acc, (_, (_, ty))| acc + ty.type_size()), kind: ComponentInstanceTypeKind::Instantiated(component_type_id), }); Ok(types.push_anon(ty)) } fn instantiate_exports( &mut self, exports: Vec, types: &mut TypeAlloc, offset: usize, ) -> Result { fn insert_export( name: &str, export: ComponentEntityType, exports: &mut IndexMap, ComponentEntityType)>, type_size: &mut u32, offset: usize, ) -> Result<()> { let name = to_kebab_str(name, "instance export", offset)?; match exports.entry(name.to_owned()) { Entry::Occupied(e) => bail!( offset, "instance export name `{name}` conflicts with previous export name `{prev}`", prev = e.key() ), Entry::Vacant(e) => { *type_size = combine_type_sizes(*type_size, export.type_size(), offset)?; e.insert((None, export)); } } Ok(()) } let mut type_size = 1; let mut inst_exports = IndexMap::new(); for export in exports { assert!(export.ty.is_none()); match export.kind { ComponentExternalKind::Module => { insert_export( export.name, ComponentEntityType::Module(self.module_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?; } ComponentExternalKind::Component => { insert_export( export.name, ComponentEntityType::Component(self.component_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?; } ComponentExternalKind::Instance => { insert_export( export.name, ComponentEntityType::Instance(self.instance_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?; } ComponentExternalKind::Func => { insert_export( export.name, ComponentEntityType::Func(self.function_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?; } ComponentExternalKind::Value => { insert_export( export.name, ComponentEntityType::Value(*self.value_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?; } ComponentExternalKind::Type => { let ty = self.type_at(export.index, false, offset)?; insert_export( export.name, ComponentEntityType::Type { referenced: ty, // The created type index here isn't used anywhere // in index spaces because a "bag of exports" // doesn't build up its own index spaces. Just fill // in the same index here in this case as what's // referenced. created: ty, }, &mut inst_exports, &mut type_size, offset, )?; } } } let ty = Type::ComponentInstance(ComponentInstanceType { type_size, kind: ComponentInstanceTypeKind::Exports(inst_exports), }); Ok(types.push_anon(ty)) } fn instantiate_core_exports( &mut self, exports: Vec, types: &mut TypeAlloc, offset: usize, ) -> Result { fn insert_export( name: &str, export: EntityType, exports: &mut IndexMap, type_size: &mut u32, offset: usize, ) -> Result<()> { *type_size = combine_type_sizes(*type_size, export.type_size(), offset)?; if exports.insert(name.to_string(), export).is_some() { bail!( offset, "duplicate instantiation export name `{name}` already defined", ) } Ok(()) } let mut type_size = 1; let mut inst_exports = IndexMap::new(); for export in exports { match export.kind { ExternalKind::Func => { insert_export( export.name, EntityType::Func(self.core_function_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?; } ExternalKind::Table => insert_export( export.name, EntityType::Table(*self.table_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?, ExternalKind::Memory => insert_export( export.name, EntityType::Memory(*self.memory_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?, ExternalKind::Global => { insert_export( export.name, EntityType::Global(*self.global_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?; } ExternalKind::Tag => insert_export( export.name, EntityType::Tag(self.core_function_at(export.index, offset)?), &mut inst_exports, &mut type_size, offset, )?, } } let ty = Type::Instance(InstanceType { type_size, kind: InstanceTypeKind::Exports(inst_exports), }); Ok(types.push_anon(ty)) } fn alias_core_instance_export( &mut self, instance_index: u32, kind: ExternalKind, name: &str, types: &TypeList, offset: usize, ) -> Result<()> { macro_rules! push_module_export { ($expected:path, $collection:ident, $ty:literal) => {{ match self.core_instance_export(instance_index, name, types, offset)? { $expected(ty) => { self.$collection.push(*ty); Ok(()) } _ => { bail!( offset, "export `{name}` for core instance {instance_index} is not a {}", $ty ) } } }}; } match kind { ExternalKind::Func => { check_max( self.function_count(), 1, MAX_WASM_FUNCTIONS, "functions", offset, )?; push_module_export!(EntityType::Func, core_funcs, "function") } ExternalKind::Table => { check_max(self.core_tables.len(), 1, MAX_WASM_TABLES, "tables", offset)?; push_module_export!(EntityType::Table, core_tables, "table") } ExternalKind::Memory => { check_max( self.core_memories.len(), 1, MAX_WASM_MEMORIES, "memories", offset, )?; push_module_export!(EntityType::Memory, core_memories, "memory") } ExternalKind::Global => { check_max( self.core_globals.len(), 1, MAX_WASM_GLOBALS, "globals", offset, )?; push_module_export!(EntityType::Global, core_globals, "global") } ExternalKind::Tag => { check_max(self.core_tags.len(), 1, MAX_WASM_TAGS, "tags", offset)?; push_module_export!(EntityType::Tag, core_tags, "tag") } } } fn alias_instance_export( &mut self, instance_index: u32, kind: ComponentExternalKind, name: &str, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let name = to_kebab_str(name, "alias export", offset)?; macro_rules! push_component_export { ($expected:path, $collection:ident, $ty:literal) => {{ match self.instance_export(instance_index, name, types, offset)? { $expected(ty) => { self.$collection.push(*ty); Ok(()) } _ => { bail!( offset, "export `{name}` for instance {instance_index} is not a {}", $ty ) } } }}; } match kind { ComponentExternalKind::Module => { check_max( self.core_modules.len(), 1, MAX_WASM_MODULES, "modules", offset, )?; push_component_export!(ComponentEntityType::Module, core_modules, "module") } ComponentExternalKind::Component => { check_max( self.components.len(), 1, MAX_WASM_COMPONENTS, "components", offset, )?; push_component_export!(ComponentEntityType::Component, components, "component") } ComponentExternalKind::Instance => { check_max( self.instance_count(), 1, MAX_WASM_INSTANCES, "instances", offset, )?; push_component_export!(ComponentEntityType::Instance, instances, "instance") } ComponentExternalKind::Func => { check_max( self.function_count(), 1, MAX_WASM_FUNCTIONS, "functions", offset, )?; push_component_export!(ComponentEntityType::Func, funcs, "function") } ComponentExternalKind::Value => { check_max(self.values.len(), 1, MAX_WASM_VALUES, "values", offset)?; match self.instance_export(instance_index, name, types, offset)? { ComponentEntityType::Value(ty) => { self.values.push((*ty, false)); Ok(()) } _ => bail!( offset, "export `{name}` for instance {instance_index} is not a value", ), } } ComponentExternalKind::Type => { check_max(self.type_count(), 1, MAX_WASM_TYPES, "types", offset)?; match *self.instance_export(instance_index, name, types, offset)? { ComponentEntityType::Type { created, .. } => { let id = types.with_unique(created); self.types.push(id); Ok(()) } _ => { bail!( offset, "export `{name}` for instance {instance_index} is not a type", ) } } } } } fn alias_module(components: &mut [Self], count: u32, index: u32, offset: usize) -> Result<()> { let component = Self::check_alias_count(components, count, offset)?; let ty = component.module_at(index, offset)?; let current = components.last_mut().unwrap(); check_max( current.core_modules.len(), 1, MAX_WASM_MODULES, "modules", offset, )?; current.core_modules.push(ty); Ok(()) } fn alias_component( components: &mut [Self], count: u32, index: u32, offset: usize, ) -> Result<()> { let component = Self::check_alias_count(components, count, offset)?; let ty = component.component_at(index, offset)?; let current = components.last_mut().unwrap(); check_max( current.components.len(), 1, MAX_WASM_COMPONENTS, "components", offset, )?; current.components.push(ty); Ok(()) } fn alias_core_type( components: &mut [Self], count: u32, index: u32, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let component = Self::check_alias_count(components, count, offset)?; let ty = component.type_at(index, true, offset)?; let current = components.last_mut().unwrap(); check_max(current.type_count(), 1, MAX_WASM_TYPES, "types", offset)?; let id = types.with_unique(ty); current.core_types.push(id); Ok(()) } fn alias_type( components: &mut [Self], count: u32, index: u32, types: &mut TypeAlloc, offset: usize, ) -> Result<()> { let component = Self::check_alias_count(components, count, offset)?; let ty = component.type_at(index, false, offset)?; let current = components.last_mut().unwrap(); check_max(current.type_count(), 1, MAX_WASM_TYPES, "types", offset)?; let id = types.with_unique(ty); current.types.push(id); Ok(()) } fn check_alias_count(components: &[Self], count: u32, offset: usize) -> Result<&Self> { let count = count as usize; if count >= components.len() { bail!(offset, "invalid outer alias count of {count}"); } Ok(&components[components.len() - count - 1]) } fn create_defined_type( &self, ty: crate::ComponentDefinedType, types: &TypeList, offset: usize, ) -> Result { match ty { crate::ComponentDefinedType::Primitive(ty) => Ok(ComponentDefinedType::Primitive(ty)), crate::ComponentDefinedType::Record(fields) => { self.create_record_type(fields.as_ref(), types, offset) } crate::ComponentDefinedType::Variant(cases) => { self.create_variant_type(cases.as_ref(), types, offset) } crate::ComponentDefinedType::List(ty) => Ok(ComponentDefinedType::List( self.create_component_val_type(ty, types, offset)?, )), crate::ComponentDefinedType::Tuple(tys) => { self.create_tuple_type(tys.as_ref(), types, offset) } crate::ComponentDefinedType::Flags(names) => { self.create_flags_type(names.as_ref(), offset) } crate::ComponentDefinedType::Enum(cases) => { self.create_enum_type(cases.as_ref(), offset) } crate::ComponentDefinedType::Union(tys) => { self.create_union_type(tys.as_ref(), types, offset) } crate::ComponentDefinedType::Option(ty) => Ok(ComponentDefinedType::Option( self.create_component_val_type(ty, types, offset)?, )), crate::ComponentDefinedType::Result { ok, err } => Ok(ComponentDefinedType::Result { ok: ok .map(|ty| self.create_component_val_type(ty, types, offset)) .transpose()?, err: err .map(|ty| self.create_component_val_type(ty, types, offset)) .transpose()?, }), } } fn create_record_type( &self, fields: &[(&str, crate::ComponentValType)], types: &TypeList, offset: usize, ) -> Result { let mut type_size = 1; let mut field_map = IndexMap::with_capacity(fields.len()); for (name, ty) in fields { let name = to_kebab_str(name, "record field", offset)?; let ty = self.create_component_val_type(*ty, types, offset)?; match field_map.entry(name.to_owned()) { Entry::Occupied(e) => bail!( offset, "record field name `{name}` conflicts with previous field name `{prev}`", prev = e.key() ), Entry::Vacant(e) => { type_size = combine_type_sizes(type_size, ty.type_size(), offset)?; e.insert(ty); } } } Ok(ComponentDefinedType::Record(RecordType { type_size, fields: field_map, })) } fn create_variant_type( &self, cases: &[crate::VariantCase], types: &TypeList, offset: usize, ) -> Result { let mut type_size = 1; let mut case_map: IndexMap = IndexMap::with_capacity(cases.len()); if cases.is_empty() { return Err(BinaryReaderError::new( "variant type must have at least one case", offset, )); } if cases.len() > u32::MAX as usize { return Err(BinaryReaderError::new( "variant type cannot be represented with a 32-bit discriminant value", offset, )); } for (i, case) in cases.iter().enumerate() { if let Some(refines) = case.refines { if refines >= i as u32 { return Err(BinaryReaderError::new( "variant case can only refine a previously defined case", offset, )); } } let name = to_kebab_str(case.name, "variant case", offset)?; let ty = case .ty .map(|ty| self.create_component_val_type(ty, types, offset)) .transpose()?; match case_map.entry(name.to_owned()) { Entry::Occupied(e) => bail!( offset, "variant case name `{name}` conflicts with previous case name `{prev}`", name = case.name, prev = e.key() ), Entry::Vacant(e) => { type_size = combine_type_sizes( type_size, ty.map(|ty| ty.type_size()).unwrap_or(1), offset, )?; // Safety: the use of `KebabStr::new_unchecked` here is safe because the string // was already verified to be kebab case. e.insert(VariantCase { ty, refines: case .refines .map(|i| KebabStr::new_unchecked(cases[i as usize].name).to_owned()), }); } } } Ok(ComponentDefinedType::Variant(VariantType { type_size, cases: case_map, })) } fn create_tuple_type( &self, tys: &[crate::ComponentValType], types: &TypeList, offset: usize, ) -> Result { let mut type_size = 1; let types = tys .iter() .map(|ty| { let ty = self.create_component_val_type(*ty, types, offset)?; type_size = combine_type_sizes(type_size, ty.type_size(), offset)?; Ok(ty) }) .collect::>()?; Ok(ComponentDefinedType::Tuple(TupleType { type_size, types })) } fn create_flags_type(&self, names: &[&str], offset: usize) -> Result { let mut names_set = IndexSet::with_capacity(names.len()); for name in names { let name = to_kebab_str(name, "flag", offset)?; if !names_set.insert(name.to_owned()) { bail!( offset, "flag name `{name}` conflicts with previous flag name `{prev}`", prev = names_set.get(name).unwrap() ); } } Ok(ComponentDefinedType::Flags(names_set)) } fn create_enum_type(&self, cases: &[&str], offset: usize) -> Result { if cases.len() > u32::MAX as usize { return Err(BinaryReaderError::new( "enumeration type cannot be represented with a 32-bit discriminant value", offset, )); } let mut tags = IndexSet::with_capacity(cases.len()); for tag in cases { let tag = to_kebab_str(tag, "enum tag", offset)?; if !tags.insert(tag.to_owned()) { bail!( offset, "enum tag name `{tag}` conflicts with previous tag name `{prev}`", prev = tags.get(tag).unwrap() ); } } Ok(ComponentDefinedType::Enum(tags)) } fn create_union_type( &self, tys: &[crate::ComponentValType], types: &TypeList, offset: usize, ) -> Result { let mut type_size = 1; let types = tys .iter() .map(|ty| { let ty = self.create_component_val_type(*ty, types, offset)?; type_size = combine_type_sizes(type_size, ty.type_size(), offset)?; Ok(ty) }) .collect::>()?; Ok(ComponentDefinedType::Union(UnionType { type_size, types })) } fn create_component_val_type( &self, ty: crate::ComponentValType, types: &TypeList, offset: usize, ) -> Result { Ok(match ty { crate::ComponentValType::Primitive(pt) => ComponentValType::Primitive(pt), crate::ComponentValType::Type(idx) => { ComponentValType::Type(self.defined_type_at(idx, types, offset)?) } }) } pub fn type_at(&self, idx: u32, core: bool, offset: usize) -> Result { let types = if core { &self.core_types } else { &self.types }; types .get(idx as usize) .copied() .ok_or_else(|| format_err!(offset, "unknown type {idx}: type index out of bounds")) } fn function_type_at<'a>( &self, idx: u32, types: &'a TypeList, offset: usize, ) -> Result<&'a ComponentFuncType> { types[self.type_at(idx, false, offset)?] .as_component_func_type() .ok_or_else(|| format_err!(offset, "type index {idx} is not a function type")) } fn function_at(&self, idx: u32, offset: usize) -> Result { self.funcs.get(idx as usize).copied().ok_or_else(|| { format_err!( offset, "unknown function {idx}: function index out of bounds" ) }) } fn component_at(&self, idx: u32, offset: usize) -> Result { self.components.get(idx as usize).copied().ok_or_else(|| { format_err!( offset, "unknown component {idx}: component index out of bounds" ) }) } fn instance_at(&self, idx: u32, offset: usize) -> Result { self.instances.get(idx as usize).copied().ok_or_else(|| { format_err!( offset, "unknown instance {idx}: instance index out of bounds" ) }) } fn instance_export<'a>( &self, instance_index: u32, name: &KebabStr, types: &'a TypeList, offset: usize, ) -> Result<&'a ComponentEntityType> { match types[self.instance_at(instance_index, offset)?] .as_component_instance_type() .unwrap() .internal_exports(types) .get(name) { Some((_, ty)) => Ok(ty), None => bail!( offset, "instance {instance_index} has no export named `{name}`" ), } } fn value_at(&mut self, idx: u32, offset: usize) -> Result<&ComponentValType> { match self.values.get_mut(idx as usize) { Some((ty, used)) if !*used => { *used = true; Ok(ty) } Some(_) => bail!(offset, "value {idx} cannot be used more than once"), None => bail!(offset, "unknown value {idx}: value index out of bounds"), } } fn defined_type_at(&self, idx: u32, types: &TypeList, offset: usize) -> Result { let id = self.type_at(idx, false, offset)?; match &types[id] { Type::Defined(_) => Ok(id), _ => bail!(offset, "type index {} is not a defined type", idx), } } fn core_function_at(&self, idx: u32, offset: usize) -> Result { match self.core_funcs.get(idx as usize) { Some(id) => Ok(*id), None => bail!( offset, "unknown core function {idx}: function index out of bounds" ), } } fn module_at(&self, idx: u32, offset: usize) -> Result { match self.core_modules.get(idx as usize) { Some(id) => Ok(*id), None => bail!(offset, "unknown module {idx}: module index out of bounds"), } } fn core_instance_at(&self, idx: u32, offset: usize) -> Result { match self.core_instances.get(idx as usize) { Some(id) => Ok(*id), None => bail!( offset, "unknown core instance {idx}: instance index out of bounds" ), } } fn core_instance_export<'a>( &self, instance_index: u32, name: &str, types: &'a TypeList, offset: usize, ) -> Result<&'a EntityType> { match types[self.core_instance_at(instance_index, offset)?] .as_instance_type() .unwrap() .internal_exports(types) .get(name) { Some(export) => Ok(export), None => bail!( offset, "core instance {instance_index} has no export named `{name}`" ), } } fn global_at(&self, idx: u32, offset: usize) -> Result<&GlobalType> { match self.core_globals.get(idx as usize) { Some(t) => Ok(t), None => bail!(offset, "unknown global {idx}: global index out of bounds"), } } fn table_at(&self, idx: u32, offset: usize) -> Result<&TableType> { match self.core_tables.get(idx as usize) { Some(t) => Ok(t), None => bail!(offset, "unknown table {idx}: table index out of bounds"), } } fn memory_at(&self, idx: u32, offset: usize) -> Result<&MemoryType> { match self.core_memories.get(idx as usize) { Some(t) => Ok(t), None => bail!(offset, "unknown memory {idx}: memory index out of bounds"), } } fn take_component_type(&mut self) -> ComponentType { let mut ty = ComponentType { type_size: self.type_size, imports: Default::default(), exports: Default::default(), }; for (name, (url, t, kind)) in mem::take(&mut self.externs) { let map = match kind { ExternKind::Import => &mut ty.imports, ExternKind::Export => &mut ty.exports, }; let prev = map.insert(name, (url, t)); assert!(prev.is_none()); } ty } } impl Default for ComponentState { fn default() -> Self { Self { core_types: Default::default(), core_modules: Default::default(), core_instances: Default::default(), core_funcs: Default::default(), core_memories: Default::default(), core_tables: Default::default(), core_globals: Default::default(), core_tags: Default::default(), types: Default::default(), funcs: Default::default(), values: Default::default(), instances: Default::default(), components: Default::default(), externs: Default::default(), export_urls: Default::default(), import_urls: Default::default(), has_start: Default::default(), type_size: 1, } } }