diff options
Diffstat (limited to 'third_party/rust/wasmparser/src/validator.rs')
-rw-r--r-- | third_party/rust/wasmparser/src/validator.rs | 1514 |
1 files changed, 1514 insertions, 0 deletions
diff --git a/third_party/rust/wasmparser/src/validator.rs b/third_party/rust/wasmparser/src/validator.rs new file mode 100644 index 0000000000..04c207130b --- /dev/null +++ b/third_party/rust/wasmparser/src/validator.rs @@ -0,0 +1,1514 @@ +/* Copyright 2018 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + limits::*, BinaryReaderError, Encoding, FromReader, FunctionBody, HeapType, Parser, Payload, + Result, SectionLimited, ValType, WASM_COMPONENT_VERSION, WASM_MODULE_VERSION, +}; +use std::mem; +use std::ops::Range; +use std::sync::Arc; + +/// Test whether the given buffer contains a valid WebAssembly module or component, +/// analogous to [`WebAssembly.validate`][js] in the JS API. +/// +/// This functions requires the bytes to validate are entirely resident in memory. +/// Additionally this validates the given bytes with the default set of WebAssembly +/// features implemented by `wasmparser`. +/// +/// For more fine-tuned control over validation it's recommended to review the +/// documentation of [`Validator`]. +/// +/// Upon success, the type information for the top-level module or component will +/// be returned. +/// +/// [js]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/validate +pub fn validate(bytes: &[u8]) -> Result<Types> { + Validator::new().validate_all(bytes) +} + +#[test] +fn test_validate() { + assert!(validate(&[0x0, 0x61, 0x73, 0x6d, 0x1, 0x0, 0x0, 0x0]).is_ok()); + assert!(validate(&[0x0, 0x61, 0x73, 0x6d, 0x2, 0x0, 0x0, 0x0]).is_err()); +} + +mod component; +mod core; +mod func; +mod operators; +pub mod types; + +use self::component::*; +pub use self::core::ValidatorResources; +use self::core::*; +use self::types::{TypeAlloc, Types, TypesRef}; +pub use func::{FuncToValidate, FuncValidator, FuncValidatorAllocations}; +pub use operators::{Frame, FrameKind}; + +fn check_max(cur_len: usize, amt_added: u32, max: usize, desc: &str, offset: usize) -> Result<()> { + if max + .checked_sub(cur_len) + .and_then(|amt| amt.checked_sub(amt_added as usize)) + .is_none() + { + if max == 1 { + bail!(offset, "multiple {desc}"); + } + + bail!(offset, "{desc} count exceeds limit of {max}"); + } + + Ok(()) +} + +fn combine_type_sizes(a: u32, b: u32, offset: usize) -> Result<u32> { + match a.checked_add(b) { + Some(sum) if sum < MAX_WASM_TYPE_SIZE => Ok(sum), + _ => Err(format_err!( + offset, + "effective type size exceeds the limit of {MAX_WASM_TYPE_SIZE}", + )), + } +} + +/// Validator for a WebAssembly binary module or component. +/// +/// This structure encapsulates state necessary to validate a WebAssembly +/// binary. This implements validation as defined by the [core +/// specification][core]. A `Validator` is designed, like +/// [`Parser`], to accept incremental input over time. +/// Additionally a `Validator` is also designed for parallel validation of +/// functions as they are received. +/// +/// It's expected that you'll be using a [`Parser`] in tandem with a +/// `Validator`. As each [`Payload`](crate::Payload) is received from a +/// [`Parser`] you'll pass it into a `Validator` to test the validity of the +/// payload. Note that all payloads received from a [`Parser`] are expected to +/// be passed to a [`Validator`]. For example if you receive +/// [`Payload::TypeSection`](crate::Payload) you'll call +/// [`Validator::type_section`] to validate this. +/// +/// The design of [`Validator`] is intended that you'll interleave, in your own +/// application's processing, calls to validation. Each variant, after it's +/// received, will be validated and then your application would proceed as +/// usual. At all times, however, you'll have access to the [`Validator`] and +/// the validation context up to that point. This enables applications to check +/// the types of functions and learn how many globals there are, for example. +/// +/// [core]: https://webassembly.github.io/spec/core/valid/index.html +#[derive(Default)] +pub struct Validator { + /// The current state of the validator. + state: State, + + /// The global type space used by the validator and any sub-validators. + types: TypeAlloc, + + /// The module state when parsing a WebAssembly module. + module: Option<ModuleState>, + + /// With the component model enabled, this stores the pushed component states. + /// The top of the stack is the current component state. + components: Vec<ComponentState>, + + /// Enabled WebAssembly feature flags, dictating what's valid and what + /// isn't. + features: WasmFeatures, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum State { + /// A header has not yet been parsed. + /// + /// The value is the expected encoding for the header. + Unparsed(Option<Encoding>), + /// A module header has been parsed. + /// + /// The associated module state is available via [`Validator::module`]. + Module, + /// A component header has been parsed. + /// + /// The associated component state exists at the top of the + /// validator's [`Validator::components`] stack. + Component, + /// The parse has completed and no more data is expected. + End, +} + +impl State { + fn ensure_parsable(&self, offset: usize) -> Result<()> { + match self { + Self::Module | Self::Component => Ok(()), + Self::Unparsed(_) => Err(BinaryReaderError::new( + "unexpected section before header was parsed", + offset, + )), + Self::End => Err(BinaryReaderError::new( + "unexpected section after parsing has completed", + offset, + )), + } + } + + fn ensure_module(&self, section: &str, offset: usize) -> Result<()> { + self.ensure_parsable(offset)?; + + match self { + Self::Module => Ok(()), + Self::Component => Err(format_err!( + offset, + "unexpected module {section} section while parsing a component", + )), + _ => unreachable!(), + } + } + + fn ensure_component(&self, section: &str, offset: usize) -> Result<()> { + self.ensure_parsable(offset)?; + + match self { + Self::Component => Ok(()), + Self::Module => Err(format_err!( + offset, + "unexpected component {section} section while parsing a module", + )), + _ => unreachable!(), + } + } +} + +impl Default for State { + fn default() -> Self { + Self::Unparsed(None) + } +} + +/// Flags for features that are enabled for validation. +#[derive(Hash, Debug, Copy, Clone)] +pub struct WasmFeatures { + /// The WebAssembly `mutable-global` proposal (enabled by default) + pub mutable_global: bool, + /// The WebAssembly `nontrapping-float-to-int-conversions` proposal (enabled by default) + pub saturating_float_to_int: bool, + /// The WebAssembly `sign-extension-ops` proposal (enabled by default) + pub sign_extension: bool, + /// The WebAssembly reference types proposal (enabled by default) + pub reference_types: bool, + /// The WebAssembly multi-value proposal (enabled by default) + pub multi_value: bool, + /// The WebAssembly bulk memory operations proposal (enabled by default) + pub bulk_memory: bool, + /// The WebAssembly SIMD proposal (enabled by default) + pub simd: bool, + /// The WebAssembly Relaxed SIMD proposal + pub relaxed_simd: bool, + /// The WebAssembly threads proposal + pub threads: bool, + /// The WebAssembly tail-call proposal + pub tail_call: bool, + /// Whether or not floating-point instructions are enabled. + /// + /// This is enabled by default can be used to disallow floating-point + /// operators and types. + /// + /// This does not correspond to a WebAssembly proposal but is instead + /// intended for embeddings which have stricter-than-usual requirements + /// about execution. Floats in WebAssembly can have different NaN patterns + /// across hosts which can lead to host-dependent execution which some + /// runtimes may not desire. + pub floats: bool, + /// The WebAssembly multi memory proposal + pub multi_memory: bool, + /// The WebAssembly exception handling proposal + pub exceptions: bool, + /// The WebAssembly memory64 proposal + pub memory64: bool, + /// The WebAssembly extended_const proposal + pub extended_const: bool, + /// The WebAssembly component model proposal. + pub component_model: bool, + /// The WebAssembly typed function references proposal + pub function_references: bool, + /// The WebAssembly memory control proposal + pub memory_control: bool, +} + +impl WasmFeatures { + /// NOTE: This only checks that the value type corresponds to the feature set!! + /// + /// To check that reference types are valid, we need access to the module + /// types. Use module.check_value_type. + pub(crate) fn check_value_type(&self, ty: ValType) -> Result<(), &'static str> { + match ty { + ValType::I32 | ValType::I64 => Ok(()), + ValType::F32 | ValType::F64 => { + if self.floats { + Ok(()) + } else { + Err("floating-point support is disabled") + } + } + ValType::Ref(r) => { + if self.reference_types { + if !self.function_references { + match (r.heap_type, r.nullable) { + (_, false) => { + Err("function references required for non-nullable types") + } + (HeapType::TypedFunc(_), _) => { + Err("function references required for index reference types") + } + _ => Ok(()), + } + } else { + Ok(()) + } + } else { + Err("reference types support is not enabled") + } + } + ValType::V128 => { + if self.simd { + Ok(()) + } else { + Err("SIMD support is not enabled") + } + } + } + } +} + +impl Default for WasmFeatures { + fn default() -> WasmFeatures { + WasmFeatures { + // Off-by-default features. + relaxed_simd: false, + threads: false, + multi_memory: false, + exceptions: false, + memory64: false, + extended_const: false, + component_model: false, + function_references: false, + memory_control: false, + + // On-by-default features (phase 4 or greater). + mutable_global: true, + saturating_float_to_int: true, + sign_extension: true, + bulk_memory: true, + multi_value: true, + reference_types: true, + tail_call: true, + simd: true, + floats: true, + } + } +} + +/// Possible return values from [`Validator::payload`]. +#[allow(clippy::large_enum_variant)] +pub enum ValidPayload<'a> { + /// The payload validated, no further action need be taken. + Ok, + /// The payload validated, but it started a nested module or component. + /// + /// This result indicates that the specified parser should be used instead + /// of the currently-used parser until this returned one ends. + Parser(Parser), + /// A function was found to be validate. + Func(FuncToValidate<ValidatorResources>, FunctionBody<'a>), + /// The end payload was validated and the types known to the validator + /// are provided. + End(Types), +} + +impl Validator { + /// Creates a new [`Validator`] ready to validate a WebAssembly module + /// or component. + /// + /// The new validator will receive payloads parsed from + /// [`Parser`], and expects the first payload received to be + /// the version header from the parser. + pub fn new() -> Validator { + Validator::default() + } + + /// Creates a new [`Validator`] which has the specified set of wasm + /// features activated for validation. + /// + /// This function is the same as [`Validator::new`] except it also allows + /// you to customize the active wasm features in use for validation. This + /// can allow enabling experimental proposals or also turning off + /// on-by-default wasm proposals. + pub fn new_with_features(features: WasmFeatures) -> Validator { + let mut ret = Validator::new(); + ret.features = features; + ret + } + + /// Returns the wasm features used for this validator. + pub fn features(&self) -> &WasmFeatures { + &self.features + } + + /// Validates an entire in-memory module or component with this validator. + /// + /// This function will internally create a [`Parser`] to parse the `bytes` + /// provided. The entire module or component specified by `bytes` will be + /// parsed and validated. + /// + /// Upon success, the type information for the top-level module or component + /// will be returned. + pub fn validate_all(&mut self, bytes: &[u8]) -> Result<Types> { + let mut functions_to_validate = Vec::new(); + let mut last_types = None; + for payload in Parser::new(0).parse_all(bytes) { + match self.payload(&payload?)? { + ValidPayload::Func(a, b) => { + functions_to_validate.push((a, b)); + } + ValidPayload::End(types) => { + // Only the last (top-level) type information will be returned + last_types = Some(types); + } + _ => {} + } + } + + let mut allocs = FuncValidatorAllocations::default(); + for (func, body) in functions_to_validate { + let mut validator = func.into_validator(allocs); + validator.validate(&body)?; + allocs = validator.into_allocations(); + } + + Ok(last_types.unwrap()) + } + + /// Gets the types known by the validator so far within the + /// module/component `level` modules/components up from the + /// module/component currently being parsed. + /// + /// For instance, calling `validator.types(0)` will get the types of the + /// module/component currently being parsed, and `validator.types(1)` will + /// get the types of the component containing that module/component. + /// + /// Returns `None` if there is no module/component that many levels up. + pub fn types(&self, mut level: usize) -> Option<TypesRef> { + if let Some(module) = &self.module { + if level == 0 { + return Some(TypesRef::from_module(&self.types, &module.module)); + } else { + level -= 1; + } + } + + self.components + .iter() + .nth_back(level) + .map(|component| TypesRef::from_component(&self.types, component)) + } + + /// Convenience function to validate a single [`Payload`]. + /// + /// This function is intended to be used as a convenience. It will + /// internally perform any validation necessary to validate the [`Payload`] + /// provided. The convenience part is that you're likely already going to + /// be matching on [`Payload`] in your application, at which point it's more + /// appropriate to call the individual methods on [`Validator`] per-variant + /// in [`Payload`], such as [`Validator::type_section`]. + /// + /// This function returns a [`ValidPayload`] variant on success, indicating + /// one of a few possible actions that need to be taken after a payload is + /// validated. For example function contents are not validated here, they're + /// returned through [`ValidPayload`] for validation by the caller. + pub fn payload<'a>(&mut self, payload: &Payload<'a>) -> Result<ValidPayload<'a>> { + use crate::Payload::*; + match payload { + Version { + num, + encoding, + range, + } => self.version(*num, *encoding, range)?, + + // Module sections + TypeSection(s) => self.type_section(s)?, + ImportSection(s) => self.import_section(s)?, + FunctionSection(s) => self.function_section(s)?, + TableSection(s) => self.table_section(s)?, + MemorySection(s) => self.memory_section(s)?, + TagSection(s) => self.tag_section(s)?, + GlobalSection(s) => self.global_section(s)?, + ExportSection(s) => self.export_section(s)?, + StartSection { func, range } => self.start_section(*func, range)?, + ElementSection(s) => self.element_section(s)?, + DataCountSection { count, range } => self.data_count_section(*count, range)?, + CodeSectionStart { + count, + range, + size: _, + } => self.code_section_start(*count, range)?, + CodeSectionEntry(body) => { + let func_validator = self.code_section_entry(body)?; + return Ok(ValidPayload::Func(func_validator, body.clone())); + } + DataSection(s) => self.data_section(s)?, + + // Component sections + ModuleSection { parser, range, .. } => { + self.module_section(range)?; + return Ok(ValidPayload::Parser(parser.clone())); + } + InstanceSection(s) => self.instance_section(s)?, + CoreTypeSection(s) => self.core_type_section(s)?, + ComponentSection { parser, range, .. } => { + self.component_section(range)?; + return Ok(ValidPayload::Parser(parser.clone())); + } + ComponentInstanceSection(s) => self.component_instance_section(s)?, + ComponentAliasSection(s) => self.component_alias_section(s)?, + ComponentTypeSection(s) => self.component_type_section(s)?, + ComponentCanonicalSection(s) => self.component_canonical_section(s)?, + ComponentStartSection { start, range } => self.component_start_section(start, range)?, + ComponentImportSection(s) => self.component_import_section(s)?, + ComponentExportSection(s) => self.component_export_section(s)?, + + End(offset) => return Ok(ValidPayload::End(self.end(*offset)?)), + + CustomSection { .. } => {} // no validation for custom sections + UnknownSection { id, range, .. } => self.unknown_section(*id, range)?, + } + Ok(ValidPayload::Ok) + } + + /// Validates [`Payload::Version`](crate::Payload). + pub fn version(&mut self, num: u16, encoding: Encoding, range: &Range<usize>) -> Result<()> { + match &self.state { + State::Unparsed(expected) => { + if let Some(expected) = expected { + if *expected != encoding { + bail!( + range.start, + "expected a version header for a {}", + match expected { + Encoding::Module => "module", + Encoding::Component => "component", + } + ); + } + } + } + _ => { + return Err(BinaryReaderError::new( + "wasm version header out of order", + range.start, + )) + } + } + + self.state = match encoding { + Encoding::Module => { + if num == WASM_MODULE_VERSION { + assert!(self.module.is_none()); + self.module = Some(ModuleState::default()); + State::Module + } else { + bail!(range.start, "unknown binary version: {num:#x}"); + } + } + Encoding::Component => { + if !self.features.component_model { + bail!( + range.start, + "unknown binary version and encoding combination: {num:#x} and 0x1, \ + note: encoded as a component but the WebAssembly component model feature \ + is not enabled - enable the feature to allow component validation", + ); + } + if num == WASM_COMPONENT_VERSION { + self.components.push(ComponentState::default()); + State::Component + } else if num < WASM_COMPONENT_VERSION { + bail!(range.start, "unsupported component version: {num:#x}"); + } else { + bail!(range.start, "unknown component version: {num:#x}"); + } + } + }; + + Ok(()) + } + + /// Validates [`Payload::TypeSection`](crate::Payload). + pub fn type_section(&mut self, section: &crate::TypeSectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Type, + section, + "type", + |state, _, types, count, offset| { + check_max( + state.module.types.len(), + count, + MAX_WASM_TYPES, + "types", + offset, + )?; + types.reserve(count as usize); + state.module.assert_mut().types.reserve(count as usize); + Ok(()) + }, + |state, features, types, def, offset| { + state + .module + .assert_mut() + .add_type(def, features, types, offset, false /* checked above */) + }, + ) + } + + /// Validates [`Payload::ImportSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn import_section(&mut self, section: &crate::ImportSectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Import, + section, + "import", + |_, _, _, _, _| Ok(()), // add_import will check limits + |state, features, types, import, offset| { + state + .module + .assert_mut() + .add_import(import, features, types, offset) + }, + ) + } + + /// Validates [`Payload::FunctionSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn function_section(&mut self, section: &crate::FunctionSectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Function, + section, + "function", + |state, _, _, count, offset| { + check_max( + state.module.functions.len(), + count, + MAX_WASM_FUNCTIONS, + "functions", + offset, + )?; + state.module.assert_mut().functions.reserve(count as usize); + debug_assert!(state.expected_code_bodies.is_none()); + state.expected_code_bodies = Some(count); + Ok(()) + }, + |state, _, types, ty, offset| state.module.assert_mut().add_function(ty, types, offset), + ) + } + + /// Validates [`Payload::TableSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn table_section(&mut self, section: &crate::TableSectionReader<'_>) -> Result<()> { + let features = self.features; + self.process_module_section( + Order::Table, + section, + "table", + |state, _, _, count, offset| { + check_max( + state.module.tables.len(), + count, + state.module.max_tables(&features), + "tables", + offset, + )?; + state.module.assert_mut().tables.reserve(count as usize); + Ok(()) + }, + |state, features, types, table, offset| state.add_table(table, features, types, offset), + ) + } + + /// Validates [`Payload::MemorySection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn memory_section(&mut self, section: &crate::MemorySectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Memory, + section, + "memory", + |state, features, _, count, offset| { + check_max( + state.module.memories.len(), + count, + state.module.max_memories(features), + "memories", + offset, + )?; + state.module.assert_mut().memories.reserve(count as usize); + Ok(()) + }, + |state, features, _, ty, offset| { + state.module.assert_mut().add_memory(ty, features, offset) + }, + ) + } + + /// Validates [`Payload::TagSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn tag_section(&mut self, section: &crate::TagSectionReader<'_>) -> Result<()> { + if !self.features.exceptions { + return Err(BinaryReaderError::new( + "exceptions proposal not enabled", + section.range().start, + )); + } + + self.process_module_section( + Order::Tag, + section, + "tag", + |state, _, _, count, offset| { + check_max( + state.module.tags.len(), + count, + MAX_WASM_TAGS, + "tags", + offset, + )?; + state.module.assert_mut().tags.reserve(count as usize); + Ok(()) + }, + |state, features, types, ty, offset| { + state + .module + .assert_mut() + .add_tag(ty, features, types, offset) + }, + ) + } + + /// Validates [`Payload::GlobalSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn global_section(&mut self, section: &crate::GlobalSectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Global, + section, + "global", + |state, _, _, count, offset| { + check_max( + state.module.globals.len(), + count, + MAX_WASM_GLOBALS, + "globals", + offset, + )?; + state.module.assert_mut().globals.reserve(count as usize); + Ok(()) + }, + |state, features, types, global, offset| { + state.add_global(global, features, types, offset) + }, + ) + } + + /// Validates [`Payload::ExportSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn export_section(&mut self, section: &crate::ExportSectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Export, + section, + "export", + |state, _, _, count, offset| { + check_max( + state.module.exports.len(), + count, + MAX_WASM_EXPORTS, + "exports", + offset, + )?; + state.module.assert_mut().exports.reserve(count as usize); + Ok(()) + }, + |state, features, _, e, offset| { + let state = state.module.assert_mut(); + let ty = state.export_to_entity_type(&e, offset)?; + state.add_export(e.name, ty, features, offset, false /* checked above */) + }, + ) + } + + /// Validates [`Payload::StartSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn start_section(&mut self, func: u32, range: &Range<usize>) -> Result<()> { + let offset = range.start; + self.state.ensure_module("start", offset)?; + let state = self.module.as_mut().unwrap(); + state.update_order(Order::Start, offset)?; + + let ty = state.module.get_func_type(func, &self.types, offset)?; + if !ty.params().is_empty() || !ty.results().is_empty() { + return Err(BinaryReaderError::new( + "invalid start function type", + offset, + )); + } + + Ok(()) + } + + /// Validates [`Payload::ElementSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn element_section(&mut self, section: &crate::ElementSectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Element, + section, + "element", + |state, _, _, count, offset| { + check_max( + state.module.element_types.len(), + count, + MAX_WASM_ELEMENT_SEGMENTS, + "element segments", + offset, + )?; + state + .module + .assert_mut() + .element_types + .reserve(count as usize); + Ok(()) + }, + |state, features, types, e, offset| { + state.add_element_segment(e, features, types, offset) + }, + ) + } + + /// Validates [`Payload::DataCountSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn data_count_section(&mut self, count: u32, range: &Range<usize>) -> Result<()> { + let offset = range.start; + self.state.ensure_module("data count", offset)?; + + let state = self.module.as_mut().unwrap(); + state.update_order(Order::DataCount, offset)?; + + if count > MAX_WASM_DATA_SEGMENTS as u32 { + return Err(BinaryReaderError::new( + "data count section specifies too many data segments", + offset, + )); + } + + state.module.assert_mut().data_count = Some(count); + Ok(()) + } + + /// Validates [`Payload::CodeSectionStart`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn code_section_start(&mut self, count: u32, range: &Range<usize>) -> Result<()> { + let offset = range.start; + self.state.ensure_module("code", offset)?; + + let state = self.module.as_mut().unwrap(); + state.update_order(Order::Code, offset)?; + + match state.expected_code_bodies.take() { + Some(n) if n == count => {} + Some(_) => { + return Err(BinaryReaderError::new( + "function and code section have inconsistent lengths", + offset, + )); + } + // empty code sections are allowed even if the function section is + // missing + None if count == 0 => {} + None => { + return Err(BinaryReaderError::new( + "code section without function section", + offset, + )) + } + } + + // Take a snapshot of the types when we start the code section. + state.module.assert_mut().snapshot = Some(Arc::new(self.types.commit())); + + Ok(()) + } + + /// Validates [`Payload::CodeSectionEntry`](crate::Payload). + /// + /// This function will prepare a [`FuncToValidate`] which can be used to + /// create a [`FuncValidator`] to validate the function. The function body + /// provided will not be parsed or validated by this function. + /// + /// Note that the returned [`FuncToValidate`] is "connected" to this + /// [`Validator`] in that it uses the internal context of this validator for + /// validating the function. The [`FuncToValidate`] can be sent to another + /// thread, for example, to offload actual processing of functions + /// elsewhere. + /// + /// This method should only be called when parsing a module. + pub fn code_section_entry( + &mut self, + body: &crate::FunctionBody, + ) -> Result<FuncToValidate<ValidatorResources>> { + let offset = body.range().start; + self.state.ensure_module("code", offset)?; + + let state = self.module.as_mut().unwrap(); + + let (index, ty) = state.next_code_index_and_type(offset)?; + Ok(FuncToValidate::new( + index, + ty, + ValidatorResources(state.module.arc().clone()), + &self.features, + )) + } + + /// Validates [`Payload::DataSection`](crate::Payload). + /// + /// This method should only be called when parsing a module. + pub fn data_section(&mut self, section: &crate::DataSectionReader<'_>) -> Result<()> { + self.process_module_section( + Order::Data, + section, + "data", + |state, _, _, count, offset| { + state.data_segment_count = count; + check_max(0, count, MAX_WASM_DATA_SEGMENTS, "data segments", offset) + }, + |state, features, types, d, offset| state.add_data_segment(d, features, types, offset), + ) + } + + /// Validates [`Payload::ModuleSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn module_section(&mut self, range: &Range<usize>) -> Result<()> { + self.state.ensure_component("module", range.start)?; + + let current = self.components.last_mut().unwrap(); + check_max( + current.core_modules.len(), + 1, + MAX_WASM_MODULES, + "modules", + range.start, + )?; + + match mem::replace(&mut self.state, State::Unparsed(Some(Encoding::Module))) { + State::Component => {} + _ => unreachable!(), + } + + Ok(()) + } + + /// Validates [`Payload::InstanceSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn instance_section(&mut self, section: &crate::InstanceSectionReader) -> Result<()> { + self.process_component_section( + section, + "core instance", + |components, _, count, offset| { + let current = components.last_mut().unwrap(); + check_max( + current.instance_count(), + count, + MAX_WASM_INSTANCES, + "instances", + offset, + )?; + current.core_instances.reserve(count as usize); + Ok(()) + }, + |components, types, _, instance, offset| { + components + .last_mut() + .unwrap() + .add_core_instance(instance, types, offset) + }, + ) + } + + /// Validates [`Payload::CoreTypeSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn core_type_section(&mut self, section: &crate::CoreTypeSectionReader<'_>) -> Result<()> { + self.process_component_section( + section, + "core type", + |components, types, count, offset| { + let current = components.last_mut().unwrap(); + check_max(current.type_count(), count, MAX_WASM_TYPES, "types", offset)?; + types.reserve(count as usize); + current.core_types.reserve(count as usize); + Ok(()) + }, + |components, types, features, ty, offset| { + ComponentState::add_core_type( + components, ty, features, types, offset, false, /* checked above */ + ) + }, + ) + } + + /// Validates [`Payload::ComponentSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_section(&mut self, range: &Range<usize>) -> Result<()> { + self.state.ensure_component("component", range.start)?; + + let current = self.components.last_mut().unwrap(); + check_max( + current.components.len(), + 1, + MAX_WASM_COMPONENTS, + "components", + range.start, + )?; + + match mem::replace(&mut self.state, State::Unparsed(Some(Encoding::Component))) { + State::Component => {} + _ => unreachable!(), + } + + Ok(()) + } + + /// Validates [`Payload::ComponentInstanceSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_instance_section( + &mut self, + section: &crate::ComponentInstanceSectionReader, + ) -> Result<()> { + self.process_component_section( + section, + "instance", + |components, _, count, offset| { + let current = components.last_mut().unwrap(); + check_max( + current.instance_count(), + count, + MAX_WASM_INSTANCES, + "instances", + offset, + )?; + current.instances.reserve(count as usize); + Ok(()) + }, + |components, types, _, instance, offset| { + components + .last_mut() + .unwrap() + .add_instance(instance, types, offset) + }, + ) + } + + /// Validates [`Payload::ComponentAliasSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_alias_section( + &mut self, + section: &crate::ComponentAliasSectionReader<'_>, + ) -> Result<()> { + self.process_component_section( + section, + "alias", + |_, _, _, _| Ok(()), // maximums checked via `add_alias` + |components, types, _, alias, offset| -> Result<(), BinaryReaderError> { + ComponentState::add_alias(components, alias, types, offset) + }, + ) + } + + /// Validates [`Payload::ComponentTypeSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_type_section( + &mut self, + section: &crate::ComponentTypeSectionReader, + ) -> Result<()> { + self.process_component_section( + section, + "type", + |components, types, count, offset| { + let current = components.last_mut().unwrap(); + check_max(current.type_count(), count, MAX_WASM_TYPES, "types", offset)?; + types.reserve(count as usize); + current.types.reserve(count as usize); + Ok(()) + }, + |components, types, features, ty, offset| { + ComponentState::add_type( + components, ty, features, types, offset, false, /* checked above */ + ) + }, + ) + } + + /// Validates [`Payload::ComponentCanonicalSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_canonical_section( + &mut self, + section: &crate::ComponentCanonicalSectionReader, + ) -> Result<()> { + self.process_component_section( + section, + "function", + |components, _, count, offset| { + let current = components.last_mut().unwrap(); + check_max( + current.function_count(), + count, + MAX_WASM_FUNCTIONS, + "functions", + offset, + )?; + current.funcs.reserve(count as usize); + Ok(()) + }, + |components, types, _, func, offset| { + let current = components.last_mut().unwrap(); + match func { + crate::CanonicalFunction::Lift { + core_func_index, + type_index, + options, + } => current.lift_function( + core_func_index, + type_index, + options.into_vec(), + types, + offset, + ), + crate::CanonicalFunction::Lower { + func_index, + options, + } => current.lower_function(func_index, options.into_vec(), types, offset), + } + }, + ) + } + + /// Validates [`Payload::ComponentStartSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_start_section( + &mut self, + f: &crate::ComponentStartFunction, + range: &Range<usize>, + ) -> Result<()> { + self.state.ensure_component("start", range.start)?; + + // let mut section = section.clone(); + // let f = section.read()?; + + // if !section.eof() { + // return Err(BinaryReaderError::new( + // "trailing data at the end of the start section", + // section.original_position(), + // )); + // } + + self.components.last_mut().unwrap().add_start( + f.func_index, + &f.arguments, + f.results, + &self.types, + range.start, + ) + } + + /// Validates [`Payload::ComponentImportSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_import_section( + &mut self, + section: &crate::ComponentImportSectionReader, + ) -> Result<()> { + self.process_component_section( + section, + "import", + |_, _, _, _| Ok(()), // add_import will check limits + |components, types, _, import, offset| { + components + .last_mut() + .unwrap() + .add_import(import, types, offset) + }, + ) + } + + /// Validates [`Payload::ComponentExportSection`](crate::Payload). + /// + /// This method should only be called when parsing a component. + pub fn component_export_section( + &mut self, + section: &crate::ComponentExportSectionReader, + ) -> Result<()> { + self.process_component_section( + section, + "export", + |components, _, count, offset| { + let current = components.last_mut().unwrap(); + check_max( + current.externs.len(), + count, + MAX_WASM_EXPORTS, + "imports and exports", + offset, + )?; + current.externs.reserve(count as usize); + Ok(()) + }, + |components, types, _, export, offset| { + let current = components.last_mut().unwrap(); + let ty = current.export_to_entity_type(&export, types, offset)?; + current.add_export( + export.name, + export.url, + ty, + offset, + false, /* checked above */ + ) + }, + ) + } + + /// Validates [`Payload::UnknownSection`](crate::Payload). + /// + /// Currently always returns an error. + pub fn unknown_section(&mut self, id: u8, range: &Range<usize>) -> Result<()> { + Err(format_err!(range.start, "malformed section id: {id}")) + } + + /// Validates [`Payload::End`](crate::Payload). + /// + /// Returns the types known to the validator for the module or component. + pub fn end(&mut self, offset: usize) -> Result<Types> { + match std::mem::replace(&mut self.state, State::End) { + State::Unparsed(_) => Err(BinaryReaderError::new( + "cannot call `end` before a header has been parsed", + offset, + )), + State::End => Err(BinaryReaderError::new( + "cannot call `end` after parsing has completed", + offset, + )), + State::Module => { + let mut state = self.module.take().unwrap(); + state.validate_end(offset)?; + + // If there's a parent component, we'll add a module to the parent state + // and continue to validate the component + if let Some(parent) = self.components.last_mut() { + parent.add_core_module(&state.module, &mut self.types, offset)?; + self.state = State::Component; + } + + Ok(Types::from_module( + self.types.commit(), + state.module.arc().clone(), + )) + } + State::Component => { + let mut component = self.components.pop().unwrap(); + + // Validate that all values were used for the component + if let Some(index) = component.values.iter().position(|(_, used)| !*used) { + return Err( + format_err!(offset,"value index {index} was not used as part of an instantiation, start function, or export" + ) + ); + } + + // If there's a parent component, pop the stack, add it to the parent, + // and continue to validate the component + if let Some(parent) = self.components.last_mut() { + parent.add_component(&mut component, &mut self.types); + self.state = State::Component; + } + + Ok(Types::from_component(self.types.commit(), component)) + } + } + } + + fn process_module_section<'a, T>( + &mut self, + order: Order, + section: &SectionLimited<'a, T>, + name: &str, + validate_section: impl FnOnce( + &mut ModuleState, + &WasmFeatures, + &mut TypeAlloc, + u32, + usize, + ) -> Result<()>, + mut validate_item: impl FnMut( + &mut ModuleState, + &WasmFeatures, + &mut TypeAlloc, + T, + usize, + ) -> Result<()>, + ) -> Result<()> + where + T: FromReader<'a>, + { + let offset = section.range().start; + self.state.ensure_module(name, offset)?; + + let state = self.module.as_mut().unwrap(); + state.update_order(order, offset)?; + + validate_section( + state, + &self.features, + &mut self.types, + section.count(), + offset, + )?; + + for item in section.clone().into_iter_with_offsets() { + let (offset, item) = item?; + validate_item(state, &self.features, &mut self.types, item, offset)?; + } + + Ok(()) + } + + fn process_component_section<'a, T>( + &mut self, + section: &SectionLimited<'a, T>, + name: &str, + validate_section: impl FnOnce( + &mut Vec<ComponentState>, + &mut TypeAlloc, + u32, + usize, + ) -> Result<()>, + mut validate_item: impl FnMut( + &mut Vec<ComponentState>, + &mut TypeAlloc, + &WasmFeatures, + T, + usize, + ) -> Result<()>, + ) -> Result<()> + where + T: FromReader<'a>, + { + let offset = section.range().start; + + if !self.features.component_model { + return Err(BinaryReaderError::new( + "component model feature is not enabled", + offset, + )); + } + + self.state.ensure_component(name, offset)?; + validate_section( + &mut self.components, + &mut self.types, + section.count(), + offset, + )?; + + for item in section.clone().into_iter_with_offsets() { + let (offset, item) = item?; + validate_item( + &mut self.components, + &mut self.types, + &self.features, + item, + offset, + )?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::{GlobalType, MemoryType, RefType, TableType, ValType, Validator, WasmFeatures}; + use anyhow::Result; + + #[test] + fn test_module_type_information() -> Result<()> { + let bytes = wat::parse_str( + r#" + (module + (type (func (param i32 i64) (result i32))) + (memory 1 5) + (table 10 funcref) + (global (mut i32) (i32.const 0)) + (func (type 0) (i32.const 0)) + (tag (param i64 i32)) + (elem funcref (ref.func 0)) + ) + "#, + )?; + + let mut validator = Validator::new_with_features(WasmFeatures { + exceptions: true, + ..Default::default() + }); + + let types = validator.validate_all(&bytes)?; + + assert_eq!(types.type_count(), 2); + assert_eq!(types.memory_count(), 1); + assert_eq!(types.table_count(), 1); + assert_eq!(types.global_count(), 1); + assert_eq!(types.function_count(), 1); + assert_eq!(types.tag_count(), 1); + assert_eq!(types.element_count(), 1); + assert_eq!(types.module_count(), 0); + assert_eq!(types.component_count(), 0); + assert_eq!(types.instance_count(), 0); + assert_eq!(types.value_count(), 0); + + match types.func_type_at(0) { + Some(ty) => { + assert_eq!(ty.params(), [ValType::I32, ValType::I64]); + assert_eq!(ty.results(), [ValType::I32]); + } + _ => unreachable!(), + } + + match types.func_type_at(1) { + Some(ty) => { + assert_eq!(ty.params(), [ValType::I64, ValType::I32]); + assert_eq!(ty.results(), []); + } + _ => unreachable!(), + } + + assert_eq!( + types.memory_at(0), + Some(MemoryType { + memory64: false, + shared: false, + initial: 1, + maximum: Some(5) + }) + ); + + assert_eq!( + types.table_at(0), + Some(TableType { + initial: 10, + maximum: None, + element_type: RefType::FUNCREF, + }) + ); + + assert_eq!( + types.global_at(0), + Some(GlobalType { + content_type: ValType::I32, + mutable: true + }) + ); + + match types.function_at(0) { + Some(ty) => { + assert_eq!(ty.params(), [ValType::I32, ValType::I64]); + assert_eq!(ty.results(), [ValType::I32]); + } + _ => unreachable!(), + } + + match types.tag_at(0) { + Some(ty) => { + assert_eq!(ty.params(), [ValType::I64, ValType::I32]); + assert_eq!(ty.results(), []); + } + _ => unreachable!(), + } + + assert_eq!(types.element_at(0), Some(RefType::FUNCREF)); + + Ok(()) + } + + #[test] + fn test_type_id_aliasing() -> Result<()> { + let bytes = wat::parse_str( + r#" + (component + (type $T (list string)) + (alias outer 0 $T (type $A1)) + (alias outer 0 $T (type $A2)) + ) + "#, + )?; + + let mut validator = Validator::new_with_features(WasmFeatures { + component_model: true, + ..Default::default() + }); + + let types = validator.validate_all(&bytes)?; + + let t_id = types.id_from_type_index(0, false).unwrap(); + let a1_id = types.id_from_type_index(1, false).unwrap(); + let a2_id = types.id_from_type_index(2, false).unwrap(); + + // The ids should all be different + assert!(t_id != a1_id); + assert!(t_id != a2_id); + assert!(a1_id != a2_id); + + // However, they should all point to the same type + assert!(std::ptr::eq( + types.type_from_id(t_id).unwrap(), + types.type_from_id(a1_id).unwrap() + )); + assert!(std::ptr::eq( + types.type_from_id(t_id).unwrap(), + types.type_from_id(a2_id).unwrap() + )); + + Ok(()) + } +} |