From 7a39e5fac5ffb502e3c99916a97084a7217b41c8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 26 Aug 2026 07:48:15 -0700 Subject: [PATCH] Validate the size of defined component types This commit is an implementation of WebAssembly/component-model#688 where the in-memory size of component model types is now calculated and validated to be below the static threshold. The eventual goal of this implementation is to simplify logic in Wasmtime for example and reduce the affect/possibility of integer overflow when handling otherwise very large types. The limits here are large enough that all preexisting components are expected to continue to validate. --- ci/generate-spec-tests.rs | 1 - crates/wasmparser/src/limits.rs | 1 + crates/wasmparser/src/validator/component.rs | 48 +++- .../src/validator/component_types.rs | 260 +++++++++++++++++- crates/wasmparser/src/validator/types.rs | 50 +--- tests/cli/component-model/max-value-size.wast | 60 ++++ .../components/validation/max-value-size.wast | 2 +- .../validation/max-value-size.wast.stderr | 50 ---- tests/cli/wit-deep-list.wit | 16 +- .../component-model/max-value-size.wast.json | 64 +++++ .../max-value-size.wast/1.print | 4 + .../max-value-size.wast/4.print | 12 + .../max-value-size.wast/7.print | 10 + .../max-value-size.wast/8.print | 5 + 14 files changed, 473 insertions(+), 110 deletions(-) create mode 100644 tests/cli/component-model/max-value-size.wast delete mode 100644 tests/cli/spec/components/validation/max-value-size.wast.stderr create mode 100644 tests/snapshots/cli/component-model/max-value-size.wast.json create mode 100644 tests/snapshots/cli/component-model/max-value-size.wast/1.print create mode 100644 tests/snapshots/cli/component-model/max-value-size.wast/4.print create mode 100644 tests/snapshots/cli/component-model/max-value-size.wast/7.print create mode 100644 tests/snapshots/cli/component-model/max-value-size.wast/8.print diff --git a/ci/generate-spec-tests.rs b/ci/generate-spec-tests.rs index 9a971c2459..6383d5f490 100644 --- a/ci/generate-spec-tests.rs +++ b/ci/generate-spec-tests.rs @@ -87,7 +87,6 @@ fn copy_test(src: &Path, dst: &Path, features: fn(&Path) -> &str) { let directive = match dst.file_name().and_then(|s| s.to_str()) { // Disable tests by doing something like: // Some("exact-func-import.wast") => "FAIL", - Some("max-value-size.wast") => "FAIL", // not yet implemented here // Temporary exception until WebAssembly/component-model#704 lands Some("kebab.wast") => "FAIL", diff --git a/crates/wasmparser/src/limits.rs b/crates/wasmparser/src/limits.rs index 2d28a40290..2e0773f849 100644 --- a/crates/wasmparser/src/limits.rs +++ b/crates/wasmparser/src/limits.rs @@ -78,6 +78,7 @@ mod component_limits { pub const MAX_WASM_FLAG_NAMES: usize = 1_000; pub const MAX_WASM_ENUM_CASES: usize = 10_000; pub const MAX_WASM_COMPONENT_TYPE_DEPTH: u32 = 100; + pub const MAX_WASM_VALUE_TYPE_BYTE_SIZE: u32 = 1 << 28; pub const MAX_WASM_INSTANTIATION_EXPORTS: usize = 100_000; pub const MAX_WASM_CANONICAL_OPTIONS: usize = 10; pub const MAX_WASM_INSTANTIATION_ARGS: usize = 100_000; diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 3a984b6859..cdead27495 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -3,7 +3,7 @@ use super::{ check_max, component_types::{ - Abi, AliasableResourceId, ComponentAnyTypeId, ComponentCoreInstanceTypeId, + Abi, AbiInfo, AliasableResourceId, ComponentAnyTypeId, ComponentCoreInstanceTypeId, ComponentCoreModuleTypeId, ComponentCoreTypeId, ComponentDefinedType, ComponentDefinedTypeId, ComponentEntityType, ComponentFuncType, ComponentFuncTypeId, ComponentInstanceType, ComponentInstanceTypeId, ComponentItem, ComponentType, @@ -3148,8 +3148,7 @@ impl ComponentState { .result .map(|ty| { let ty = self.create_component_val_type(ty, offset)?; - let ty_info = ty.info(types); - if ty_info.contains_borrow() { + if ty.abi(types).contains_borrow() { bail!(offset, "function result cannot contain a `borrow` type"); } info.combine(ty.info(types), offset)?; @@ -3984,7 +3983,8 @@ impl ComponentState { let element = self.create_component_val_type(ty, offset)?; let mut info = TypeInfo::new(); info.combine(element.info(types), offset)?; - Ok(ComponentDefinedType::List { element, info }) + let abi = AbiInfo::list(element.abi(types)); + Ok(ComponentDefinedType::List { element, info, abi }) } crate::ComponentDefinedType::Map(key, value) => { require_feature::cm_map( @@ -3997,7 +3997,13 @@ impl ComponentState { let mut info = TypeInfo::new(); info.combine(key.info(types), offset)?; info.combine(value.info(types), offset)?; - Ok(ComponentDefinedType::Map { key, value, info }) + let abi = AbiInfo::map(key.abi(types), value.abi(types)); + Ok(ComponentDefinedType::Map { + key, + value, + info, + abi, + }) } crate::ComponentDefinedType::FixedLengthList(ty, elements) => { require_feature::cm_fixed_length_lists( @@ -4021,10 +4027,12 @@ impl ComponentState { let element = self.create_component_val_type(ty, offset)?; let mut info = TypeInfo::new(); info.combine(element.info(types), offset)?; + let abi = AbiInfo::fixed_length_list(element.abi(types), elements, offset)?; Ok(ComponentDefinedType::FixedLengthList { element, length: elements, info, + abi, }) } crate::ComponentDefinedType::Tuple(tys) => { @@ -4040,7 +4048,9 @@ impl ComponentState { let ty = self.create_component_val_type(ty, offset)?; let mut info = TypeInfo::new(); info.combine(ty.info(types), offset)?; - Ok(ComponentDefinedType::Option { ty, info }) + let abis = [None, Some(ty.abi(types))]; + let abi = AbiInfo::variant(abis.into_iter(), offset)?; + Ok(ComponentDefinedType::Option { ty, info, abi }) } crate::ComponentDefinedType::Result { ok, err } => { let ok = ok @@ -4056,7 +4066,9 @@ impl ComponentState { if let Some(ty) = &err { info.combine(ty.info(types), offset)?; } - Ok(ComponentDefinedType::Result { ok, err, info }) + let abis = [ok.map(|ty| ty.abi(types)), err.map(|ty| ty.abi(types))]; + let abi = AbiInfo::variant(abis.into_iter(), offset)?; + Ok(ComponentDefinedType::Result { ok, err, info, abi }) } crate::ComponentDefinedType::Own(idx) => Ok(ComponentDefinedType::Own( self.resource_at(idx, types, offset)?, @@ -4077,7 +4089,8 @@ impl ComponentState { if let Some(ty) = &ty { info.combine(ty.info(types), offset)?; } - Ok(ComponentDefinedType::Future { ty, info }) + let abi = AbiInfo::future_or_stream(ty.map(|ty| ty.abi(types))); + Ok(ComponentDefinedType::Future { ty, info, abi }) } crate::ComponentDefinedType::Stream(ty) => { require_feature::cm_async( @@ -4107,7 +4120,8 @@ impl ComponentState { if let Some(ty) = &ty { info.combine(ty.info(types), offset)?; } - Ok(ComponentDefinedType::Stream { ty, info }) + let abi = AbiInfo::future_or_stream(ty.map(|ty| ty.abi(types))); + Ok(ComponentDefinedType::Stream { ty, info, abi }) } } } @@ -4143,8 +4157,10 @@ impl ComponentState { } } + let abi = AbiInfo::record(field_map.values().map(|ty| ty.abi(types)), offset)?; Ok(ComponentDefinedType::Record(RecordType { info, + abi, fields: field_map, })) } @@ -4194,8 +4210,13 @@ impl ComponentState { } } + let abi = AbiInfo::variant( + case_map.values().map(|c| c.ty.map(|ty| ty.abi(types))), + offset, + )?; Ok(ComponentDefinedType::Variant(VariantType { info, + abi, cases: case_map, })) } @@ -4210,7 +4231,7 @@ impl ComponentState { if tys.is_empty() { bail!(offset, "tuple type must have at least one type"); } - let types = tys + let tuple_types: Box<[_]> = tys .iter() .map(|ty| { let ty = self.create_component_val_type(*ty, offset)?; @@ -4219,7 +4240,12 @@ impl ComponentState { }) .collect::>()?; - Ok(ComponentDefinedType::Tuple(TupleType { info, types })) + let abi = AbiInfo::record(tuple_types.iter().map(|ty| ty.abi(types)), offset)?; + Ok(ComponentDefinedType::Tuple(TupleType { + info, + abi, + types: tuple_types, + })) } fn create_flags_type(&self, names: &[&str], offset: u64) -> Result { diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index c619cb7f2e..9f77e58903 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -2,6 +2,7 @@ use super::component::ExternKind; use super::{CanonicalOptions, Concurrency}; +use crate::limits::MAX_WASM_VALUE_TYPE_BYTE_SIZE; use crate::validator::StringEncoding; use crate::validator::component::PtrSize; use crate::validator::names::KebabString; @@ -754,6 +755,14 @@ impl ComponentValType { } } + /// Returns the Canonical ABI information for this value type. + pub(crate) fn abi(&self, types: &TypeList) -> AbiInfo { + match self { + Self::Primitive(ty) => AbiInfo::primitive(*ty), + Self::Type(id) => types[*id].abi(), + } + } + fn lower_gc( &self, types: &TypeList, @@ -1384,6 +1393,8 @@ pub struct VariantCase { pub struct RecordType { /// Metadata about this record type. pub(crate) info: TypeInfo, + /// Canonical ABI information about this record type. + pub(crate) abi: AbiInfo, /// The map of record fields. pub fields: IndexMap, } @@ -1414,6 +1425,8 @@ impl RecordType { pub struct VariantType { /// Metadata about this variant type. pub(crate) info: TypeInfo, + /// Canonical ABI information about this variant type. + pub(crate) abi: AbiInfo, /// The map of variant cases. pub cases: IndexMap, } @@ -1461,6 +1474,8 @@ fn lower_gc_sum_type( pub struct TupleType { /// Metadata about this tuple type. pub(crate) info: TypeInfo, + /// Canonical ABI information about this tuple type. + pub(crate) abi: AbiInfo, /// The types of the tuple. pub types: Box<[ComponentValType]>, } @@ -1501,6 +1516,8 @@ pub enum ComponentDefinedType { element: ComponentValType, /// Cached type information. info: TypeInfo, + /// Cached Canonical ABI information. + abi: AbiInfo, }, /// The type is a map. Map { @@ -1510,6 +1527,8 @@ pub enum ComponentDefinedType { value: ComponentValType, /// Cached type information. info: TypeInfo, + /// Cached Canonical ABI information. + abi: AbiInfo, }, /// The type is a fixed-length list. FixedLengthList { @@ -1519,6 +1538,8 @@ pub enum ComponentDefinedType { length: u32, /// Cached type information. info: TypeInfo, + /// Cached Canonical ABI information. + abi: AbiInfo, }, /// The type is a tuple. Tuple(TupleType), @@ -1532,6 +1553,8 @@ pub enum ComponentDefinedType { ty: ComponentValType, /// Cached type information. info: TypeInfo, + /// Cached Canonical ABI information. + abi: AbiInfo, }, /// The type is a `result`. Result { @@ -1541,6 +1564,8 @@ pub enum ComponentDefinedType { err: Option, /// Cached type information. info: TypeInfo, + /// Cached Canonical ABI information. + abi: AbiInfo, }, /// The type is an owned handle to the specified resource. Own(AliasableResourceId), @@ -1552,6 +1577,8 @@ pub enum ComponentDefinedType { ty: Option, /// Cached type information. info: TypeInfo, + /// Cached Canonical ABI information. + abi: AbiInfo, }, /// A stream type with the specified payload type. Stream { @@ -1559,6 +1586,8 @@ pub enum ComponentDefinedType { ty: Option, /// Cached type information. info: TypeInfo, + /// Cached Canonical ABI information. + abi: AbiInfo, }, } @@ -1567,8 +1596,11 @@ impl TypeData for ComponentDefinedType { const IS_CORE_SUB_TYPE: bool = false; fn type_info(&self, _types: &TypeList) -> TypeInfo { match self { - Self::Primitive(_) | Self::Flags(_) | Self::Enum(_) | Self::Own(_) => TypeInfo::new(), - Self::Borrow(_) => TypeInfo::borrow(), + Self::Primitive(_) + | Self::Flags(_) + | Self::Enum(_) + | Self::Own(_) + | Self::Borrow(_) => TypeInfo::new(), Self::Record(r) => r.info, Self::Variant(v) => v.info, Self::Tuple(t) => t.info, @@ -1583,7 +1615,231 @@ impl TypeData for ComponentDefinedType { } } +/// ABI information about defined value types in the component model. +/// +/// This is packed into a single `u32` as it's stored on every defined type: +/// +/// * bits 0..28 - `elem_size(t, 'i64')`, the number of bytes a value of this +/// type takes up when stored in a 64-bit linear memory. Validation requires +/// this to be less than `MAX_WASM_VALUE_TYPE_BYTE_SIZE`. Note that this only +/// tracks 64-bit linear memory sizes because ABI information isn't needed in +/// validation except for ensuring sizes are beneath a certain threshold. +/// +/// * bits 28..30 - the base-2 logarithm of `alignment(t, 'i64')`, so 0, 1, 2, +/// or 3 for an alignment of 1, 2, 4, or 8. +/// +/// * bit 30 - whether or not this type transitively contains a `borrow`. For +/// example `(borrow $t)` and `(list (borrow $t))` both do but `(list u32)` +/// does not. Used to validate that component function results don't contain +/// borrows. +/// +/// * bit 31 - unused. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[doc(hidden)] +pub struct AbiInfo(u32); + +impl AbiInfo { + const ELEM_SIZE_BITS: u32 = 28; + const ALIGN_LOG2_BITS: u32 = 2; + const CONTAINS_BORROW_BITS: u32 = 1; + + const ELEM_SIZE_OFFSET: u32 = 0; + const ALIGN_LOG2_OFFSET: u32 = Self::ELEM_SIZE_OFFSET + Self::ELEM_SIZE_BITS; + const CONTAINS_BORROW_OFFSET: u32 = Self::ALIGN_LOG2_OFFSET + Self::ALIGN_LOG2_BITS; + + const ELEM_SIZE_MASK: u32 = ((1 << Self::ELEM_SIZE_BITS) - 1) << Self::ELEM_SIZE_OFFSET; + const ALIGN_LOG2_MASK: u32 = ((1 << Self::ALIGN_LOG2_BITS) - 1) << Self::ALIGN_LOG2_OFFSET; + const CONTAINS_BORROW_MASK: u32 = + ((1 << Self::CONTAINS_BORROW_BITS) - 1) << Self::CONTAINS_BORROW_OFFSET; + + const BOOL: AbiInfo = AbiInfo::new_unchecked(1, 1, false); + const U8: AbiInfo = AbiInfo::new_unchecked(1, 1, false); + const U16: AbiInfo = AbiInfo::new_unchecked(2, 2, false); + const U32: AbiInfo = AbiInfo::new_unchecked(4, 4, false); + const U64: AbiInfo = AbiInfo::new_unchecked(8, 8, false); + const S8: AbiInfo = AbiInfo::U8; + const S16: AbiInfo = AbiInfo::U16; + const S32: AbiInfo = AbiInfo::U32; + const S64: AbiInfo = AbiInfo::U64; + const F32: AbiInfo = AbiInfo::U32; + const F64: AbiInfo = AbiInfo::U64; + const CHAR: AbiInfo = AbiInfo::U32; + const ERROR_CONTEXT: AbiInfo = AbiInfo::U32; + const PTR_PAIR: AbiInfo = AbiInfo::new_unchecked(16, 8, false); + const HANDLE: AbiInfo = AbiInfo::new_unchecked(4, 4, false); + const BORROW: AbiInfo = AbiInfo::HANDLE.with_borrow(true); + + pub(crate) fn primitive(ty: PrimitiveValType) -> AbiInfo { + match ty { + PrimitiveValType::Bool => AbiInfo::BOOL, + PrimitiveValType::S8 => AbiInfo::S8, + PrimitiveValType::U8 => AbiInfo::U8, + PrimitiveValType::S16 => AbiInfo::S16, + PrimitiveValType::U16 => AbiInfo::U16, + PrimitiveValType::S32 => AbiInfo::S32, + PrimitiveValType::U32 => AbiInfo::U32, + PrimitiveValType::S64 => AbiInfo::S64, + PrimitiveValType::U64 => AbiInfo::U64, + PrimitiveValType::F32 => AbiInfo::F32, + PrimitiveValType::F64 => AbiInfo::F64, + PrimitiveValType::Char => AbiInfo::CHAR, + PrimitiveValType::ErrorContext => AbiInfo::ERROR_CONTEXT, + PrimitiveValType::String => AbiInfo::PTR_PAIR, + } + } + + pub(crate) fn flags(count: usize) -> AbiInfo { + if count <= 8 { + AbiInfo::U8 + } else if count <= 16 { + AbiInfo::U16 + } else { + AbiInfo::U32 + } + } + + pub(crate) fn discriminant(cases: usize) -> AbiInfo { + if cases <= 0x100 { + AbiInfo::U8 + } else if cases <= 0x10000 { + AbiInfo::U16 + } else { + AbiInfo::U32 + } + } + + pub(crate) fn record(fields: impl Iterator, offset: u64) -> Result { + let mut size = 0; + let mut align = 1; + let mut contains_borrow = false; + for field in fields { + size = align_to(size, field.alignment()) + u64::from(field.elem_size()); + align = align.max(field.alignment()); + contains_borrow |= field.contains_borrow(); + } + debug_assert!(size > 0); + AbiInfo::new(align_to(size, align), align, contains_borrow, offset) + } + + pub(crate) fn variant( + payloads: impl ExactSizeIterator>, + offset: u64, + ) -> Result { + let discriminant = AbiInfo::discriminant(payloads.len()); + let mut payload_size = 0; + let mut payload_align = 1; + let mut contains_borrow = false; + for payload in payloads.flatten() { + payload_size = payload_size.max(u64::from(payload.elem_size())); + payload_align = payload_align.max(payload.alignment()); + contains_borrow |= payload.contains_borrow(); + } + let align = discriminant.alignment().max(payload_align); + let size = align_to(u64::from(discriminant.elem_size()), payload_align) + payload_size; + AbiInfo::new(align_to(size, align), align, contains_borrow, offset) + } + + pub(crate) fn list(element: AbiInfo) -> AbiInfo { + AbiInfo::PTR_PAIR.with_borrow(element.contains_borrow()) + } + + pub(crate) fn map(key: AbiInfo, value: AbiInfo) -> AbiInfo { + AbiInfo::PTR_PAIR.with_borrow(key.contains_borrow() || value.contains_borrow()) + } + + pub(crate) fn fixed_length_list(element: AbiInfo, length: u32, offset: u64) -> Result { + // Note that this multiplication cannot overflow since it's a 32x32-bit + // multiplication done in the 64-bit integer space. + let size = u64::from(element.elem_size()) * u64::from(length); + AbiInfo::new(size, element.alignment(), element.contains_borrow(), offset) + } + + pub(crate) fn future_or_stream(payload: Option) -> AbiInfo { + AbiInfo::HANDLE.with_borrow(match payload { + Some(abi) => abi.contains_borrow(), + None => false, + }) + } + + /// Creates an `AbiInfo` of `size` bytes with an alignment of `align`, returning + /// an error if the size is at or above the component model's limit. + fn new(size: u64, align: u32, contains_borrow: bool, offset: u64) -> Result { + if size >= u64::from(MAX_WASM_VALUE_TYPE_BYTE_SIZE) { + bail!( + offset, + "value type's maximum in-memory size exceeds maximum byte size" + ); + } + Ok(AbiInfo::new_unchecked(size as u32, align, contains_borrow)) + } + + /// Same as `new` but for sizes which are statically known to be in-bounds. + const fn new_unchecked(size: u32, align: u32, contains_borrow: bool) -> AbiInfo { + debug_assert!(size < MAX_WASM_VALUE_TYPE_BYTE_SIZE); + debug_assert!(align.is_power_of_two() && align <= 8); + AbiInfo( + (size << Self::ELEM_SIZE_OFFSET) + | (align.trailing_zeros() << Self::ALIGN_LOG2_OFFSET) + | ((contains_borrow as u32) << Self::CONTAINS_BORROW_OFFSET), + ) + } + + const fn with_borrow(&self, contains_borrow: bool) -> AbiInfo { + AbiInfo( + (self.0 & !Self::CONTAINS_BORROW_MASK) + | ((contains_borrow as u32) << Self::CONTAINS_BORROW_OFFSET), + ) + } + + /// The size of this value as it resides in a 64-bit linear memory. + fn elem_size(&self) -> u32 { + (self.0 & Self::ELEM_SIZE_MASK) >> Self::ELEM_SIZE_OFFSET + } + + /// The alignment of this value as it resides in a 64-bit linear memory. + fn alignment(&self) -> u32 { + 1 << ((self.0 & Self::ALIGN_LOG2_MASK) >> Self::ALIGN_LOG2_OFFSET) + } + + /// Whether this type transitively contains a `borrow`. + pub(crate) fn contains_borrow(&self) -> bool { + (self.0 & Self::CONTAINS_BORROW_MASK) != 0 + } +} + +const _: () = { + assert!(1 << AbiInfo::ELEM_SIZE_BITS >= MAX_WASM_VALUE_TYPE_BYTE_SIZE); +}; + +/// The Canonical ABI's `align_to`. +fn align_to(offset: u64, align: u32) -> u64 { + debug_assert!(align.is_power_of_two()); + let align = u64::from(align); + (offset + (align - 1)) & !(align - 1) +} + impl ComponentDefinedType { + /// Returns the Canonical ABI information for this type. + pub(crate) fn abi(&self) -> AbiInfo { + match self { + Self::Primitive(ty) => AbiInfo::primitive(*ty), + Self::Flags(names) => AbiInfo::flags(names.len()), + Self::Enum(cases) => AbiInfo::discriminant(cases.len()), + Self::Own(_) => AbiInfo::HANDLE, + Self::Borrow(_) => AbiInfo::BORROW, + Self::Record(r) => r.abi, + Self::Variant(v) => v.abi, + Self::Tuple(t) => t.abi, + Self::List { abi, .. } + | Self::FixedLengthList { abi, .. } + | Self::Option { abi, .. } + | Self::Map { abi, .. } + | Self::Result { abi, .. } + | Self::Future { abi, .. } + | Self::Stream { abi, .. } => *abi, + } + } + pub(crate) fn contains_ptr(&self, types: &TypeList) -> bool { match self { Self::Primitive(ty) => ty.contains_ptr(), diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index e314dde621..80773772bf 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -169,7 +169,7 @@ impl TypeData for Range { /// Metadata about a type and its transitive structure. /// -/// Currently contains three properties: +/// Currently contains two properties: /// /// * The "size" of a type - a proxy to the recursive size of a type if /// everything in the type were unique (e.g. no shared references). Not an @@ -184,11 +184,6 @@ impl TypeData for Range { /// for example to prevent stack overflow. All types start with depth 1, and /// for example `(list u32)` would have depth 2. /// -/// * Whether or not a type contains a "borrow" transitively inside of it. For -/// example `(borrow $t)` and `(list (borrow $t))` both contain borrows, but -/// `(list u32)` does not. Used to validate that component function results do -/// not contain borrows. -/// /// Currently this is represented as a compact 32-bit integer to ensure that /// `TypeId`, which this is stored in, remains relatively small. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -204,52 +199,32 @@ impl TypeInfo { const SIZE_OFFSET: u32 = 0; const DEPTH_OFFSET: u32 = Self::SIZE_OFFSET + Self::SIZE_BITS; - const CONTAINS_BORROW_OFFSET: u32 = Self::DEPTH_OFFSET + Self::DEPTH_BITS; const SIZE_MASK: u32 = ((1 << Self::SIZE_BITS) - 1) << Self::SIZE_OFFSET; #[cfg(feature = "component-model")] const DEPTH_MASK: u32 = ((1 << Self::DEPTH_BITS) - 1) << Self::DEPTH_OFFSET; - #[cfg(feature = "component-model")] - const CONTAINS_BORROW_MASK: u32 = 1 << Self::CONTAINS_BORROW_OFFSET; const MAX_SIZE: u32 = (1 << Self::SIZE_BITS) - 1; const MAX_DEPTH: u32 = (1 << Self::DEPTH_BITS) - 1; - const _ASSERT: () = { - assert!(Self::CONTAINS_BORROW_OFFSET < 32); - #[cfg(feature = "component-model")] - assert!(Self::MAX_DEPTH > crate::limits::MAX_WASM_COMPONENT_TYPE_DEPTH); - assert!(Self::MAX_SIZE > crate::limits::MAX_WASM_TYPE_SIZE); - }; /// Creates a new blank set of type information. /// /// Defaults to size 1 to ensure that this consumes space in the final type /// structure. pub(crate) fn new() -> TypeInfo { - TypeInfo::_new(1, 1, false) - } - - /// Creates a new blank set of information about a leaf "borrow" type which - /// has size 1. - #[cfg(feature = "component-model")] - pub(crate) fn borrow() -> TypeInfo { - TypeInfo::_new(1, 1, true) + TypeInfo::_new(1, 1) } /// Creates type information corresponding to a core type of the `size` - /// specified, meaning no borrows are contained within. + /// specified. pub(crate) fn core(size: u32) -> TypeInfo { - TypeInfo::_new(size, 1, false) + TypeInfo::_new(size, 1) } - fn _new(size: u32, depth: u32, contains_borrow: bool) -> TypeInfo { + fn _new(size: u32, depth: u32) -> TypeInfo { assert!(size <= Self::MAX_SIZE); assert!(depth <= Self::MAX_DEPTH); - TypeInfo( - (size << Self::SIZE_OFFSET) - | (depth << Self::DEPTH_OFFSET) - | ((contains_borrow as u32) << Self::CONTAINS_BORROW_OFFSET), - ) + TypeInfo((size << Self::SIZE_OFFSET) | (depth << Self::DEPTH_OFFSET)) } /// Combines another set of type information into this one, for example if @@ -264,8 +239,7 @@ impl TypeInfo { pub(crate) fn combine(&mut self, other: TypeInfo, offset: u64) -> Result<()> { let depth = self.depth().max(other.depth().saturating_add(1)); let size = super::combine_type_sizes(self.size(), other.size(), offset)?; - let contains_borrow = self.contains_borrow() || other.contains_borrow(); - *self = TypeInfo::_new(size, depth, contains_borrow); + *self = TypeInfo::_new(size, depth); Ok(()) } @@ -278,12 +252,14 @@ impl TypeInfo { pub(crate) fn depth(&self) -> u32 { (self.0 & Self::DEPTH_MASK) >> Self::DEPTH_OFFSET } +} +const _: () = { + assert!(TypeInfo::DEPTH_OFFSET + TypeInfo::DEPTH_BITS <= 32); + assert!(TypeInfo::MAX_SIZE > crate::limits::MAX_WASM_TYPE_SIZE); #[cfg(feature = "component-model")] - pub(crate) fn contains_borrow(&self) -> bool { - (self.0 & Self::CONTAINS_BORROW_MASK) != 0 - } -} + assert!(TypeInfo::MAX_DEPTH > crate::limits::MAX_WASM_COMPONENT_TYPE_DEPTH); +}; /// The entity type for imports and exports of a module. #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/tests/cli/component-model/max-value-size.wast b/tests/cli/component-model/max-value-size.wast new file mode 100644 index 0000000000..49af682a6f --- /dev/null +++ b/tests/cli/component-model/max-value-size.wast @@ -0,0 +1,60 @@ +;; RUN: wast % --assert default --snapshot tests/snapshots -f cm-fixed-length-lists,cm-async,cm-map + +(assert_invalid + (component + (type $a (list u8 268435447)) + (type (record (field "a" $a) (field "b" u64)))) + "exceeds maximum byte size") + +(component + (type $a (list u8 268435439)) + (type (record (field "a" $a) (field "b" u64)))) + +(assert_invalid + (component + (type $a (list u8 268435455)) + (component + (alias outer 1 0 (type $t)) + (type (tuple $t u8)))) + "exceeds maximum byte size") + +(assert_invalid + (component + (type $a (list u8 268435455)) + (import "i" (instance $i + (alias outer 1 0 (type $a2)) + (export "t" (type (eq $a2))))) + (alias export $i "t" (type $t)) + (type (list $t 2))) + "exceeds maximum byte size") + +(component + (type $a (list u8 134217727)) + (import "i" (instance $i + (alias outer 1 0 (type $a2)) + (export "t" (type (eq $a2))))) + (alias export $i "t" (type $t)) + (type (list $t 2))) + +(assert_invalid + (component + (type (component + (type (list u64 33554432))))) + "exceeds maximum byte size") + +(assert_invalid + (component + (type (instance + (type (list u64 33554432))))) + "exceeds maximum byte size") + +(component + (type $r (resource (rep i32))) + (type $big (list u8 268435455)) + (type (list (own $r) 1000)) + (type (list (future $big) 1000)) + (type (list (stream $big) 1000))) + +(component + (type $big (list u8 268435455)) + (type (list (map u8 $big) 16777215))) diff --git a/tests/cli/spec/components/validation/max-value-size.wast b/tests/cli/spec/components/validation/max-value-size.wast index bd2fde6bd9..e19ca996fc 100644 --- a/tests/cli/spec/components/validation/max-value-size.wast +++ b/tests/cli/spec/components/validation/max-value-size.wast @@ -1,4 +1,4 @@ -;; FAIL: wast \ +;; RUN: wast \ ;; --assert default \ ;; --snapshot tests/snapshots \ ;; --ignore-error-messages \ diff --git a/tests/cli/spec/components/validation/max-value-size.wast.stderr b/tests/cli/spec/components/validation/max-value-size.wast.stderr deleted file mode 100644 index c9bc6868f7..0000000000 --- a/tests/cli/spec/components/validation/max-value-size.wast.stderr +++ /dev/null @@ -1,50 +0,0 @@ -error: 7 test failures in tests/component-model/test/validation/max-value-size.wast: - --------------------------------- - -failed directive on tests/component-model/test/validation/max-value-size.wast:25:2 - -Caused by: - encoded and validated successfully but should have failed with: exceeds maximum byte size - --------------------------------- - -failed directive on tests/component-model/test/validation/max-value-size.wast:31:2 - -Caused by: - encoded and validated successfully but should have failed with: exceeds maximum byte size - --------------------------------- - -failed directive on tests/component-model/test/validation/max-value-size.wast:37:2 - -Caused by: - encoded and validated successfully but should have failed with: exceeds maximum byte size - --------------------------------- - -failed directive on tests/component-model/test/validation/max-value-size.wast:43:2 - -Caused by: - encoded and validated successfully but should have failed with: exceeds maximum byte size - --------------------------------- - -failed directive on tests/component-model/test/validation/max-value-size.wast:48:2 - -Caused by: - encoded and validated successfully but should have failed with: exceeds maximum byte size - --------------------------------- - -failed directive on tests/component-model/test/validation/max-value-size.wast:57:2 - -Caused by: - encoded and validated successfully but should have failed with: exceeds maximum byte size - --------------------------------- - -failed directive on tests/component-model/test/validation/max-value-size.wast:63:2 - -Caused by: - encoded and validated successfully but should have failed with: exceeds maximum byte size diff --git a/tests/cli/wit-deep-list.wit b/tests/cli/wit-deep-list.wit index 26c4b54e4f..9bafaf649c 100644 --- a/tests/cli/wit-deep-list.wit +++ b/tests/cli/wit-deep-list.wit @@ -4,14 +4,14 @@ package a:b; world lists { - type t = list; - type t2 = list; - type t3 = list; - type t4 = list; - type t5 = list; - type t6 = list; - type t7 = list; - type t8 = list; + type t = list; + type t2 = list; + type t3 = list; + type t4 = list; + type t5 = list; + type t6 = list; + type t7 = list; + type t8 = list; import x: func(t: t); } diff --git a/tests/snapshots/cli/component-model/max-value-size.wast.json b/tests/snapshots/cli/component-model/max-value-size.wast.json new file mode 100644 index 0000000000..d9bb3ae1c3 --- /dev/null +++ b/tests/snapshots/cli/component-model/max-value-size.wast.json @@ -0,0 +1,64 @@ +{ + "source_filename": "tests/cli/component-model/max-value-size.wast", + "commands": [ + { + "type": "assert_invalid", + "line": 4, + "filename": "max-value-size.0.wasm", + "module_type": "binary", + "text": "exceeds maximum byte size" + }, + { + "type": "module", + "line": 9, + "filename": "max-value-size.1.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 14, + "filename": "max-value-size.2.wasm", + "module_type": "binary", + "text": "exceeds maximum byte size" + }, + { + "type": "assert_invalid", + "line": 22, + "filename": "max-value-size.3.wasm", + "module_type": "binary", + "text": "exceeds maximum byte size" + }, + { + "type": "module", + "line": 31, + "filename": "max-value-size.4.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 40, + "filename": "max-value-size.5.wasm", + "module_type": "binary", + "text": "exceeds maximum byte size" + }, + { + "type": "assert_invalid", + "line": 46, + "filename": "max-value-size.6.wasm", + "module_type": "binary", + "text": "exceeds maximum byte size" + }, + { + "type": "module", + "line": 51, + "filename": "max-value-size.7.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 58, + "filename": "max-value-size.8.wasm", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/cli/component-model/max-value-size.wast/1.print b/tests/snapshots/cli/component-model/max-value-size.wast/1.print new file mode 100644 index 0000000000..7d4baec052 --- /dev/null +++ b/tests/snapshots/cli/component-model/max-value-size.wast/1.print @@ -0,0 +1,4 @@ +(component + (type $a (;0;) (list u8 268435439)) + (type (;1;) (record (field "a" $a) (field "b" u64))) +) diff --git a/tests/snapshots/cli/component-model/max-value-size.wast/4.print b/tests/snapshots/cli/component-model/max-value-size.wast/4.print new file mode 100644 index 0000000000..7dc2a8c944 --- /dev/null +++ b/tests/snapshots/cli/component-model/max-value-size.wast/4.print @@ -0,0 +1,12 @@ +(component + (type $a (;0;) (list u8 134217727)) + (type (;1;) + (instance + (alias outer 1 $a (type (;0;))) + (export (;1;) "t" (type (eq 0))) + ) + ) + (import "i" (instance $i (;0;) (type 1))) + (alias export $i "t" (type $t (;2;))) + (type (;3;) (list $t 2)) +) diff --git a/tests/snapshots/cli/component-model/max-value-size.wast/7.print b/tests/snapshots/cli/component-model/max-value-size.wast/7.print new file mode 100644 index 0000000000..5ff07fb9ce --- /dev/null +++ b/tests/snapshots/cli/component-model/max-value-size.wast/7.print @@ -0,0 +1,10 @@ +(component + (type $r (;0;) (resource (rep i32))) + (type $big (;1;) (list u8 268435455)) + (type (;2;) (own $r)) + (type (;3;) (list 2 1000)) + (type (;4;) (future $big)) + (type (;5;) (list 4 1000)) + (type (;6;) (stream $big)) + (type (;7;) (list 6 1000)) +) diff --git a/tests/snapshots/cli/component-model/max-value-size.wast/8.print b/tests/snapshots/cli/component-model/max-value-size.wast/8.print new file mode 100644 index 0000000000..3d172f7ed5 --- /dev/null +++ b/tests/snapshots/cli/component-model/max-value-size.wast/8.print @@ -0,0 +1,5 @@ +(component + (type $big (;0;) (list u8 268435455)) + (type (;1;) (map u8 $big)) + (type (;2;) (list 1 16777215)) +)