pub(crate) struct ModuleState { /// Internal state that is incrementally built-up for the module being /// validated. This houses type information for all wasm items, like /// functions. Note that this starts out as a solely owned `Arc<T>` so we can /// get mutable access, but after we get to the code section this is never /// mutated to we can clone it cheaply and hand it to sub-validators. pub module: arc::MaybeOwned<Module>,
const_expr_allocs: OperatorValidatorAllocations,
/// When parsing the code section, represents the current index in the section.
code_section_index: Option<usize>,
}
pubfn next_code_index_and_type(&mutself) -> (u32, u32) { let index = self
.code_section_index
.get_or_insert(self.module.num_imported_functions as usize);
assert!(*index < self.module.functions.len());
let ty = self.module.functions[*index];
*index += 1;
match &table.init {
TableInit::RefNull => { if !table.ty.element_type.is_nullable() {
bail!(offset, "type mismatch: non-defaultable element type");
}
}
TableInit::Expr(expr) => { if !self.module.features.function_references() {
bail!(
offset, "tables with expression initializers require \
the function-references proposal"
);
} self.check_const_expr(expr, table.ty.element_type.into(), types)?;
}
} self.module.assert_mut().tables.push(table.ty);
Ok(())
}
pubfn add_data_segment(&mutself, data: Data, types: &TypeList, offset: usize) -> Result<()> { match data.kind {
DataKind::Passive => { if !self.module.features.bulk_memory() {
bail!(
offset, "passive data segments require the bulk-memory proposal"
);
}
Ok(())
}
DataKind::Active {
memory_index,
offset_expr,
} => { let ty = self.module.memory_at(memory_index, offset)?.index_type(); self.check_const_expr(&offset_expr, ty, types)
}
}
}
pubfn add_element_segment(
&mutself, mut e: Element,
types: &TypeList,
offset: usize,
) -> Result<()> { // the `funcref` value type is allowed all the way back to the MVP, so // don't check it here let element_ty = match &mut e.items { crate::ElementItems::Functions(_) => RefType::FUNC, crate::ElementItems::Expressions(ty, _) => { self.module.check_ref_type(ty, offset)?;
*ty
}
};
match e.kind {
ElementKind::Active {
table_index,
offset_expr,
} => { let table = self.module.table_at(table_index.unwrap_or(0), offset)?; if !types.reftype_is_subtype(element_ty, table.element_type) { return Err(BinaryReaderError::new(
format!( "type mismatch: invalid element type `{}` for table type `{}`",
ty_to_str(element_ty.into()),
ty_to_str(table.element_type.into()),
),
offset,
));
}
self.check_const_expr(&offset_expr, table.index_type(), types)?;
}
ElementKind::Passive | ElementKind::Declared => { if !self.module.features.bulk_memory() { return Err(BinaryReaderError::new( "bulk memory must be enabled",
offset,
));
}
}
}
let validate_count = |count: u32| -> Result<(), BinaryReaderError> { if count > MAX_WASM_TABLE_ENTRIES as u32 {
Err(BinaryReaderError::new( "number of elements is out of bounds",
offset,
))
} else {
Ok(())
}
}; match e.items { crate::ElementItems::Functions(reader) => { let count = reader.count();
validate_count(count)?; for f in reader.into_iter_with_offsets() { let (offset, f) = f?; self.module.get_func_type(f, types, offset)?; self.module.assert_mut().function_references.insert(f);
}
} crate::ElementItems::Expressions(ty, reader) => {
validate_count(reader.count())?; for expr in reader { self.check_const_expr(&expr?, ValType::Ref(ty), types)?;
}
}
} self.module.assert_mut().element_types.push(element_ty);
Ok(())
}
fn validate_global(&mutself, index: u32) -> Result<()> { let module = &self.resources.module; let global = module.global_at(index, self.offset)?;
if index >= module.num_imported_globals && !self.ops.features.gc() { return Err(BinaryReaderError::new( "constant expression required: global.get of locally defined global", self.offset,
));
} if global.mutable { return Err(BinaryReaderError::new( "constant expression required: global.get of mutable global", self.offset,
));
}
Ok(())
}
// Functions in initialization expressions are only valid in // element segment initialization expressions and globals. In // these contexts we want to record all function references. // // Initialization expressions can also be found in the data // section, however. A `RefFunc` instruction in those situations // is always invalid and needs to produce a validation error. In // this situation, though, we can no longer modify // the state since it's been "snapshot" already for // parallel validation of functions. // // If we cannot modify the function references then this function // *should* result in a validation error, but we defer that // validation error to happen later. The `uninserted_funcref` // boolean here is used to track this and will cause a panic // (aka a fuzz bug) if we somehow forget to emit an error somewhere // else. fn insert_ref_func(&mutself, index: u32) { ifself.resources.module.as_mut().is_none() { self.uninserted_funcref = true;
} else { self.resources
.module
.assert_mut()
.function_references
.insert(index);
}
}
// `global.get` is a valid const expression for imported, immutable // globals.
(@visit $self:ident visit_global_get $idx:ident) => {{
$self.validate_global($idx)?;
$self.validator().visit_global_get($idx)
}}; // `ref.func`, if it's in a `global` initializer, will insert into // the set of referenced functions so it's processed here.
(@visit $self:ident visit_ref_func $idx:ident) => {{
$self.insert_ref_func($idx);
$self.validator().visit_ref_func($idx)
}};
fn check_table_type(&self, ty: &mut TableType, types: &TypeList, offset: usize) -> Result<()> { // The `funcref` value type is allowed all the way back to the MVP, so // don't check it here. if ty.element_type != RefType::FUNCREF { self.check_ref_type(&mut ty.element_type, offset)?
}
self.check_limits(ty.initial, ty.maximum, offset)?; if ty.table64 && !self.features().memory64() {
bail!(offset, "memory64 must be enabled for 64-bit tables");
} if ty.shared && !self.features().shared_everything_threads() {
bail!(
offset, "shared tables require the shared-everything-threads proposal"
);
}
let true_maximum = if ty.table64 {
u64::MAX
} else {
u32::MAX as u64
}; let err = format!("table size must be at most {true_maximum:#x} entries"); if ty.initial > true_maximum { return Err(BinaryReaderError::new(err, offset));
} iflet Some(maximum) = ty.maximum { if maximum > true_maximum { return Err(BinaryReaderError::new(err, offset));
}
} if ty.shared && !types.reftype_is_shared(ty.element_type) { return Err(BinaryReaderError::new( "shared tables must have a shared element type",
offset,
));
}
Ok(())
}
if ty.memory64 && !self.features().memory64() {
bail!(offset, "memory64 must be enabled for 64-bit memories");
} if ty.shared && !self.features().threads() {
bail!(offset, "threads must be enabled for shared memories");
}
let page_size = iflet Some(page_size_log2) = ty.page_size_log2 { if !self.features().custom_page_sizes() { return Err(BinaryReaderError::new( "the custom page sizes proposal must be enabled to customize a memory's page size",
offset,
));
} // Currently 2**0 and 2**16 are the only valid page sizes, but this // may be relaxed to allow any power of two in the future. if page_size_log2 != 0 && page_size_log2 != 16 { return Err(BinaryReaderError::new("invalid custom page size", offset));
} let page_size = 1_u64 << page_size_log2;
debug_assert!(page_size.is_power_of_two());
debug_assert!(page_size == DEFAULT_WASM_PAGE_SIZE || page_size == 1);
page_size
} else { let page_size_log2 = 16;
debug_assert_eq!(DEFAULT_WASM_PAGE_SIZE, 1 << page_size_log2);
DEFAULT_WASM_PAGE_SIZE
}; let true_maximum = if ty.memory64 {
max_wasm_memory64_pages(page_size)
} else {
max_wasm_memory32_pages(page_size)
}; let err = format!("memory size must be at most {true_maximum:#x} {page_size}-byte pages"); if ty.initial > true_maximum { return Err(BinaryReaderError::new(err, offset));
} iflet Some(maximum) = ty.maximum { if maximum > true_maximum { return Err(BinaryReaderError::new(err, offset));
}
} if ty.shared && ty.maximum.is_none() { return Err(BinaryReaderError::new( "shared memory must have maximum size",
offset,
));
}
Ok(())
}
#[cfg(feature = "component-model")] pub(crate) fn imports_for_module_type(
&self,
offset: usize,
) -> Result<IndexMap<(String, String), EntityType>> { // Ensure imports are unique, which is a requirement of the component model: // https://github.com/WebAssembly/component-model/blob/d09f907/design/mvp/Explainer.md#import-and-export-definitions self.imports
.iter()
.map(|((module, name), types)| { if types.len() != 1 {
bail!(
offset, "module has a duplicate import name `{module}:{name}` \
that is not allowed in components",
);
}
Ok(((module.clone(), name.clone()), types[0]))
})
.collect::<Result<_>>()
}
fn check_value_type(&self, ty: &mut ValType, offset: usize) -> Result<()> { // The above only checks the value type for features. // We must check it if it's a reference. match ty {
ValType::Ref(rt) => self.check_ref_type(rt, offset),
_ => self
.features
.check_value_type(*ty)
.map_err(|e| BinaryReaderError::new(e, offset)),
}
}
fn check_heap_type(&self, ty: &mut HeapType, offset: usize) -> Result<()> { // Check that the heap type is valid. let type_index = match ty {
HeapType::Abstract { .. } => return Ok(()),
HeapType::Concrete(type_index) | HeapType::Exact(type_index) => type_index,
}; match type_index {
UnpackedIndex::Module(idx) => { let id = self.type_id_at(*idx, offset)?;
*type_index = UnpackedIndex::Id(id);
Ok(())
} // Types at this stage should not be canonicalized. All // canonicalized types should already be validated meaning they // shouldn't be double-checked here again.
UnpackedIndex::RecGroup(_) | UnpackedIndex::Id(_) => unreachable!(),
}
}
fn check_tag_type(&self, ty: &TagType, types: &TypeList, offset: usize) -> Result<()> { if !self.features().exceptions() {
bail!(offset, "exceptions proposal not enabled");
} let ty = self.func_type_at(ty.func_type_idx, types, offset)?; if !ty.results().is_empty() && !self.features.stack_switching() { return Err(BinaryReaderError::new( "invalid exception type: non-empty tag result type",
offset,
));
}
Ok(())
}
fn check_global_type(
&self,
ty: &mut GlobalType,
types: &TypeList,
offset: usize,
) -> Result<()> { self.check_value_type(&mut ty.content_type, offset)?; if ty.shared && !self.features.shared_everything_threads() {
bail!(
offset, "shared globals require the shared-everything-threads proposal"
);
} if ty.shared && !types.valtype_is_shared(ty.content_type) { return Err(BinaryReaderError::new( "shared globals must have a shared value type",
offset,
));
}
Ok(())
}
fn check_limits<T>(&self, initial: T, maximum: Option<T>, offset: usize) -> Result<()> where
T: Into<u64>,
{ iflet Some(max) = maximum { if initial.into() > max.into() { return Err(BinaryReaderError::new( "size minimum must not be greater than maximum",
offset,
));
}
}
Ok(())
}
fn type_id_at(&self, idx: u32, offset: usize) -> Result<CoreTypeId> { self.types
.get(idx as usize)
.copied()
.ok_or_else(|| format_err!(offset, "unknown type {idx}: type index out of bounds"))
}
/// The implementation of [`WasmModuleResources`] used by /// [`Validator`](crate::Validator). #[derive(Debug)] pubstruct ValidatorResources(pub(crate) Arc<Module>);
impl WasmModuleResources for ValidatorResources { fn table_at(&self, at: u32) -> Option<TableType> { self.0.tables.get(at as usize).cloned()
}
fn sub_type_at(&self, at: u32) -> Option<&SubType> { let id = *self.0.types.get(at as usize)?; let types = self.0.snapshot.as_ref().unwrap();
Some(&types[id])
}
let inner = core::mem::replace(&mutself.inner, Inner::Empty); let x = match inner {
Inner::Owned(x) => x,
_ => Self::unreachable(),
}; let x = Arc::new(x); self.inner = Inner::Shared(x);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.