/* Copyright 2019 Mozilla Foundation * *LicensedundertheApacheLicense,Version2.0(the"License"); *youmaynotusethisfileexceptincompliancewiththeLicense. *YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense.
*/
// The basic validation algorithm here is copied from the "Validation // Algorithm" section of the WebAssembly specification - // https://webassembly.github.io/spec/core/appendix/algorithm.html. // // That algorithm is followed pretty closely here, namely `push_operand`, // `pop_operand`, `push_ctrl`, and `pop_ctrl`. If anything here is a bit // confusing it's recommended to read over that section to see how it maps to // the various methods here.
// This is a list of flags for wasm features which are used to gate various // instructions. pub(crate) features: WasmFeatures,
// Temporary storage used during `match_stack_operands`
popped_types_tmp: Vec<MaybeType>,
/// The `control` list is the list of blocks that we're currently in.
control: Vec<Frame>, /// The `operands` is the current type stack.
operands: Vec<MaybeType>, /// When local_inits is modified, the relevant index is recorded here to be /// undone when control pops
inits: Vec<u32>,
/// Offset of the `end` instruction which emptied the `control` stack, which /// must be the end of the function.
end_which_emptied_control: Option<usize>,
/// Whether validation is happening in a shared context.
shared: bool,
// No science was performed in the creation of this number, feel free to change // it if you so like. const MAX_LOCALS_TO_TRACK: usize = 50;
pub(super) struct Locals { // Total number of locals in the function.
num_locals: u32,
// The first MAX_LOCALS_TO_TRACK locals in a function. This is used to // optimize the theoretically common case where most functions don't have // many locals and don't need a full binary search in the entire local space // below.
first: Vec<ValType>,
// This is a "compressed" list of locals for this function. The list of // locals are represented as a list of tuples. The second element is the // type of the local, and the first element is monotonically increasing as // you visit elements of this list. The first element is the maximum index // of the local, after the previous index, of the type specified. // // This allows us to do a binary search on the list for a local's index for // `local.{get,set,tee}`. We do a binary search for the index desired, and // it either lies in a "hole" where the maximum index is specified later, // or it's at the end of the list meaning it's out of bounds.
all: Vec<(u32, ValType)>,
}
/// A Wasm control flow block on the control flow stack during Wasm validation. // // # Dev. Note // // This structure corresponds to `ctrl_frame` as specified at in the validation // appendix of the wasm spec #[derive(Debug, Copy, Clone)] pubstruct Frame { /// Indicator for what kind of instruction pushed this frame. pub kind: FrameKind, /// The type signature of this frame, represented as a singular return type /// or a type index pointing into the module's types. pub block_type: BlockType, /// The index, below which, this frame cannot modify the operand stack. pub height: usize, /// Whether this frame is unreachable so far. pub unreachable: bool, /// The number of initializations in the stack at the time of its creation pub init_height: usize,
}
/// Type storage within the validator. /// /// When managing the operand stack in unreachable code, the validator may not /// fully know an operand's type. this unknown state is known as the `bottom` /// type in the WebAssembly specification. Validating further instructions may /// give us more information; either partial (`PartialRef`) or fully known. #[derive(Debug, Copy, Clone)] enum MaybeType<T = ValType> { /// The operand has no available type information due to unreachable code. /// /// This state represents "unknown" and corresponds to the `bottom` type in /// the WebAssembly specification. There are no constraints on what this /// type may be and it can match any other type during validation.
Bottom, /// The operand is known to be a reference and we may know its abstract /// type. /// /// This state is not fully `Known`, however, because its type can be /// interpreted as either: /// - `shared` or not-`shared` /// - nullable or not nullable /// /// No further refinements are required for WebAssembly instructions today /// but this may grow in the future.
UnknownRef(Option<AbstractHeapType>), /// The operand is known to have type `T`.
Known(T),
}
// The validator is pretty performance-sensitive and `MaybeType` is the main // unit of storage, so assert that it doesn't exceed 4 bytes which is the // current expected size. const _: () = {
assert!(core::mem::size_of::<MaybeType>() == 4);
};
/// Creates a new operator validator which will be used to validate a /// function whose type is the `ty` index specified. /// /// The `resources` are used to learn about the function type underlying /// `ty`. pubfn new_func<T>(
ty: u32,
offset: usize,
features: &WasmFeatures,
resources: &T,
allocs: OperatorValidatorAllocations,
) -> Result<Self> where
T: WasmModuleResources,
{ letmut ret = OperatorValidator::new(features, allocs);
ret.control.push(Frame {
kind: FrameKind::Block,
block_type: BlockType::FuncType(ty),
height: 0,
unreachable: false,
init_height: 0,
});
// Retrieve the function's type via index (`ty`); the `offset` is // necessary due to `sub_type_at`'s error messaging. let sub_ty = OperatorValidatorTemp {
offset,
inner: &mut ret,
resources,
}
.sub_type_at(ty)?;
// Set up the function's locals. iflet CompositeInnerType::Func(func_ty) = &sub_ty.composite_type.inner { for ty in func_ty.params() {
ret.locals.define(1, *ty);
ret.local_inits.push(true);
}
} else {
bail!(offset, "expected func type at index {ty}, found {sub_ty}")
}
// If we're in a shared function, ensure we do not access unshared // objects. if sub_ty.composite_type.shared {
ret.shared = true;
}
Ok(ret)
}
/// Creates a new operator validator which will be used to validate an /// `init_expr` constant expression which should result in the `ty` /// specified. pubfn new_const_expr(
features: &WasmFeatures,
ty: ValType,
allocs: OperatorValidatorAllocations,
) -> Self { letmut ret = OperatorValidator::new(features, allocs);
ret.control.push(Frame {
kind: FrameKind::Block,
block_type: BlockType::Type(ty),
height: 0,
unreachable: false,
init_height: 0,
});
ret
}
/// Returns the current operands stack height. pubfn operand_stack_height(&self) -> usize { self.operands.len()
}
/// Returns the optional value type of the value operand at the given /// `depth` from the top of the operand stack. /// /// - Returns `None` if the `depth` is out of bounds. /// - Returns `Some(None)` if there is a value with unknown type /// at the given `depth`. /// /// # Note /// /// A `depth` of 0 will refer to the last operand on the stack. pubfn peek_operand_at(&self, depth: usize) -> Option<Option<ValType>> {
Some(matchself.operands.iter().rev().nth(depth)? {
MaybeType::Known(t) => Some(*t),
MaybeType::Bottom | MaybeType::UnknownRef(..) => None,
})
}
/// Returns the number of frames on the control flow stack. pubfn control_stack_height(&self) -> usize { self.control.len()
}
pubfn finish(&mutself, offset: usize) -> Result<()> { ifself.control.last().is_some() {
bail!(
offset, "control frames remain at end of function: END opcode expected"
);
}
// The `end` opcode is one byte which means that the `offset` here // should point just beyond the `end` opcode which emptied the control // stack. If not that means more instructions were present after the // control stack was emptied. if offset != self.end_which_emptied_control.unwrap() + 1 { return Err(self.err_beyond_end(offset));
}
Ok(())
}
fn err_beyond_end(&self, offset: usize) -> BinaryReaderError {
format_err!(offset, "operators remaining after end of function")
}
impl<'resources, R> OperatorValidatorTemp<'_, 'resources, R> where
R: WasmModuleResources,
{ /// Pushes a type onto the operand stack. /// /// This is used by instructions to represent a value that is pushed to the /// operand stack. This can fail, but only if `Type` is feature gated. /// Otherwise the push operation always succeeds. fn push_operand<T>(&mutself, ty: T) -> Result<()> where
T: Into<MaybeType>,
{ let maybe_ty = ty.into();
if cfg!(debug_assertions) { match maybe_ty {
MaybeType::Known(ValType::Ref(r)) => match r.heap_type() {
HeapType::Concrete(index) => {
debug_assert!(
matches!(index, UnpackedIndex::Id(_)), "only ref types referencing `CoreTypeId`s can \
be pushed to the operand stack"
);
}
_ => {}
},
_ => {}
}
}
// Canonicalize the module index into an id. self.resources.check_heap_type(&mut heap_ty, self.offset)?;
debug_assert!(matches!(heap_ty, HeapType::Concrete(UnpackedIndex::Id(_))));
let ref_ty = RefType::new(nullable, heap_ty).ok_or_else(|| {
format_err!(self.offset, "implementation limit: type index too large")
})?;
// Canonicalize the module index into an id. self.resources.check_heap_type(&mut heap_ty, self.offset)?;
debug_assert!(matches!(heap_ty, HeapType::Concrete(UnpackedIndex::Id(_))));
let ref_ty = RefType::new(nullable, heap_ty).ok_or_else(|| {
format_err!(self.offset, "implementation limit: type index too large")
})?;
self.pop_operand(Some(ref_ty.into()))
}
/// Pop the given label types, checking that they are indeed present on the /// stack, and then push them back on again. fn pop_push_label_types(
&mutself,
label_types: impl PreciseIterator<Item = ValType>,
) -> Result<()> { for ty in label_types.clone().rev() { self.pop_operand(Some(ty))?;
} for ty in label_types { self.push_operand(ty)?;
}
Ok(())
}
/// Attempts to pop a type from the operand stack. /// /// This function is used to remove types from the operand stack. The /// `expected` argument can be used to indicate that a type is required, or /// simply that something is needed to be popped. /// /// If `expected` is `Some(T)` then this will be guaranteed to return /// `T`, and it will only return success if the current block is /// unreachable or if `T` was found at the top of the operand stack. /// /// If `expected` is `None` then it indicates that something must be on the /// operand stack, but it doesn't matter what's on the operand stack. This /// is useful for polymorphic instructions like `select`. /// /// If `Some(T)` is returned then `T` was popped from the operand stack and /// matches `expected`. If `None` is returned then it means that `None` was /// expected and a type was successfully popped, but its exact type is /// indeterminate because the current block is unreachable. fn pop_operand(&mutself, expected: Option<ValType>) -> Result<MaybeType> { // This method is one of the hottest methods in the validator so to // improve codegen this method contains a fast-path success case where // if the top operand on the stack is as expected it's returned // immediately. This is the most common case where the stack will indeed // have the expected type and all we need to do is pop it off. // // Note that this still has to be careful to be correct, though. For // efficiency an operand is unconditionally popped and on success it is // matched against the state of the world to see if we could actually // pop it. If we shouldn't have popped it then it's passed to the slow // path to get pushed back onto the stack. let popped = matchself.operands.pop() {
Some(MaybeType::Known(actual_ty)) => { if Some(actual_ty) == expected { iflet Some(control) = self.control.last() { ifself.operands.len() >= control.height { self.record_pop(); return Ok(MaybeType::Known(actual_ty));
}
}
}
Some(MaybeType::Known(actual_ty))
}
other => other,
};
self._pop_operand(expected, popped)
}
// This is the "real" implementation of `pop_operand` which is 100% // spec-compliant with little attention paid to efficiency since this is the // slow-path from the actual `pop_operand` function above. #[cold] fn _pop_operand(
&mutself,
expected: Option<ValType>,
popped: Option<MaybeType>,
) -> Result<MaybeType> { self.operands.extend(popped); let control = matchself.control.last() {
Some(c) => c,
None => return Err(self.err_beyond_end(self.offset)),
}; let actual = ifself.operands.len() == control.height && control.unreachable {
MaybeType::Bottom
} else { ifself.operands.len() == control.height { let desc = match expected {
Some(ty) => ty_to_str(ty),
None => "a type".into(),
};
bail!( self.offset, "type mismatch: expected {desc} but nothing on stack"
)
} else { self.operands.pop().unwrap()
}
}; iflet Some(expected) = expected { match (actual, expected) { // The bottom type matches all expectations
(MaybeType::Bottom, _) => {}
// The "heap bottom" type only matches other references types, // but not any integer types. Note that if the heap bottom is // known to have a specific abstract heap type then a subtype // check is performed against hte expected type.
(MaybeType::UnknownRef(actual_ty), ValType::Ref(expected)) => { iflet Some(actual) = actual_ty { let expected_shared = self.resources.is_shared(expected); let actual = RefType::new( false,
HeapType::Abstract {
shared: expected_shared,
ty: actual,
},
)
.unwrap(); if !self.resources.is_subtype(actual.into(), expected.into()) {
bail!( self.offset, "type mismatch: expected {}, found {}",
ty_to_str(expected.into()),
ty_to_str(actual.into())
);
}
}
}
// Use the `is_subtype` predicate to test if a found type matches // the expectation.
(MaybeType::Known(actual), expected) => { if !self.resources.is_subtype(actual, expected) {
bail!( self.offset, "type mismatch: expected {}, found {}",
ty_to_str(expected),
ty_to_str(actual)
);
}
}
// A "heap bottom" type cannot match any numeric types.
(
MaybeType::UnknownRef(..),
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128,
) => {
bail!( self.offset, "type mismatch: expected {}, found heap type",
ty_to_str(expected)
)
}
}
} self.record_pop();
Ok(actual)
}
/// Match expected vs. actual operand. fn match_operand(
&mutself,
actual: ValType,
expected: ValType,
) -> Result<(), BinaryReaderError> { #[cfg(debug_assertions)] let tmp = self.pop_push_count; self.push_operand(actual)?; self.pop_operand(Some(expected))?; #[cfg(debug_assertions)]
{ self.pop_push_count = tmp;
}
Ok(())
}
/// Match a type sequence to the top of the stack. fn match_stack_operands(
&mutself,
expected_tys: impl PreciseIterator<Item = ValType> + 'resources,
) -> Result<()> {
debug_assert!(self.popped_types_tmp.is_empty()); self.popped_types_tmp.reserve(expected_tys.len()); #[cfg(debug_assertions)] let tmp = self.pop_push_count; for expected_ty in expected_tys.rev() { let actual_ty = self.pop_operand(Some(expected_ty))?; self.popped_types_tmp.push(actual_ty);
} for ty inself.inner.popped_types_tmp.drain(..).rev() { self.inner.operands.push(ty.into());
} #[cfg(debug_assertions)]
{ self.pop_push_count = tmp;
}
Ok(())
}
/// Pop a reference type from the operand stack. fn pop_ref(&mutself, expected: Option<RefType>) -> Result<MaybeType<RefType>> { matchself.pop_operand(expected.map(|t| t.into()))? {
MaybeType::Bottom => Ok(MaybeType::UnknownRef(None)),
MaybeType::UnknownRef(ty) => Ok(MaybeType::UnknownRef(ty)),
MaybeType::Known(ValType::Ref(rt)) => Ok(MaybeType::Known(rt)),
MaybeType::Known(ty) => bail!( self.offset, "type mismatch: expected ref but found {}",
ty_to_str(ty)
),
}
}
/// Pop a reference type from the operand stack, checking if it is a subtype /// of a nullable type of `expected` or the shared version of `expected`. /// /// This function returns the popped reference type and its `shared`-ness, /// saving extra lookups for concrete types. fn pop_maybe_shared_ref(&mutself, expected: AbstractHeapType) -> Result<MaybeType<RefType>> { let actual = matchself.pop_ref(None)? {
MaybeType::Bottom => return Ok(MaybeType::Bottom),
MaybeType::UnknownRef(None) => return Ok(MaybeType::UnknownRef(None)),
MaybeType::UnknownRef(Some(actual)) => { if !actual.is_subtype_of(expected) {
bail!( self.offset, "type mismatch: expected subtype of {}, found {}",
expected.as_str(false),
actual.as_str(false),
)
} return Ok(MaybeType::UnknownRef(Some(actual)));
}
MaybeType::Known(ty) => ty,
}; // Change our expectation based on whether we're dealing with an actual // shared or unshared type. let is_actual_shared = self.resources.is_shared(actual); let expected = RefType::new( true,
HeapType::Abstract {
shared: is_actual_shared,
ty: expected,
},
)
.unwrap();
// Check (again) that the actual type is a subtype of the expected type. // Note that `_pop_operand` already does this kind of thing but we leave // that for a future refactoring (TODO). if !self.resources.is_subtype(actual.into(), expected.into()) {
bail!( self.offset, "type mismatch: expected subtype of {expected}, found {actual}",
)
}
Ok(MaybeType::Known(actual))
}
/// Fetches the type for the local at `idx`, returning an error if it's out /// of bounds. fn local(&self, idx: u32) -> Result<ValType> { matchself.locals.get(idx) {
Some(ty) => Ok(ty),
None => bail!( self.offset, "unknown local {}: local index out of bounds",
idx
),
}
}
/// Flags the current control frame as unreachable, additionally truncating /// the currently active operand stack. fn unreachable(&mutself) -> Result<()> { let control = matchself.control.last_mut() {
Some(frame) => frame,
None => return Err(self.err_beyond_end(self.offset)),
};
control.unreachable = true; let new_height = control.height; self.operands.truncate(new_height);
Ok(())
}
/// Pushes a new frame onto the control stack. /// /// This operation is used when entering a new block such as an if, loop, /// or block itself. The `kind` of block is specified which indicates how /// breaks interact with this block's type. Additionally the type signature /// of the block is specified by `ty`. fn push_ctrl(&mutself, kind: FrameKind, ty: BlockType) -> Result<()> { // Push a new frame which has a snapshot of the height of the current // operand stack. let height = self.operands.len(); let init_height = self.inits.len(); self.control.push(Frame {
kind,
block_type: ty,
height,
unreachable: false,
init_height,
}); // All of the parameters are now also available in this control frame, // so we push them here in order. for ty inself.params(ty)? { self.push_operand(ty)?;
}
Ok(())
}
/// Pops a frame from the control stack. /// /// This function is used when exiting a block and leaves a block scope. /// Internally this will validate that blocks have the correct result type. fn pop_ctrl(&mutself) -> Result<Frame> { // Read the expected type and expected height of the operand stack the // end of the frame. let frame = matchself.control.last() {
Some(f) => f,
None => return Err(self.err_beyond_end(self.offset)),
}; let ty = frame.block_type; let height = frame.height; let init_height = frame.init_height;
// reset_locals in the spec for init inself.inits.split_off(init_height) { self.local_inits[init as usize] = false;
}
// Pop all the result types, in reverse order, from the operand stack. // These types will, possibly, be transferred to the next frame. for ty inself.results(ty)?.rev() { self.pop_operand(Some(ty))?;
}
// Make sure that the operand stack has returned to is original // height... ifself.operands.len() != height {
bail!( self.offset, "type mismatch: values remaining on stack at end of block"
);
}
// And then we can remove it!
Ok(self.control.pop().unwrap())
}
/// Validates a relative jump to the `depth` specified. /// /// Returns the type signature of the block that we're jumping to as well /// as the kind of block if the jump is valid. Otherwise returns an error. fn jump(&self, depth: u32) -> Result<(BlockType, FrameKind)> { ifself.control.is_empty() { return Err(self.err_beyond_end(self.offset));
} match (self.control.len() - 1).checked_sub(depth as usize) {
Some(i) => { let frame = &self.control[i];
Ok((frame.block_type, frame.kind))
}
None => bail!(self.offset, "unknown label: branch depth too large"),
}
}
/// Validates that `memory_index` is valid in this module, and returns the /// type of address used to index the memory specified. fn check_memory_index(&self, memory_index: u32) -> Result<ValType> { matchself.resources.memory_at(memory_index) {
Some(mem) => Ok(mem.index_type()),
None => bail!(self.offset, "unknown memory {}", memory_index),
}
}
/// Validates a `memarg for alignment and such (also the memory it /// references), and returns the type of index used to address the memory. fn check_memarg(&self, memarg: MemArg) -> Result<ValType> { let index_ty = self.check_memory_index(memarg.memory)?; if memarg.align > memarg.max_align {
bail!( self.offset, "malformed memop alignment: alignment must not be larger than natural"
);
} if index_ty == ValType::I32 && memarg.offset > u64::from(u32::MAX) {
bail!(self.offset, "offset out of range: must be <= 2**32");
}
Ok(index_ty)
}
fn check_shared_memarg(&self, memarg: MemArg) -> Result<ValType> { if memarg.align != memarg.max_align {
bail!( self.offset, "atomic instructions must always specify maximum alignment"
);
} self.check_memory_index(memarg.memory)
}
fn check_simd_lane_index(&self, index: u8, max: u8) -> Result<()> { if index >= max {
bail!(self.offset, "SIMD index out of bounds");
}
Ok(())
}
/// Validates a block type, primarily with various in-flight proposals. fn check_block_type(&self, ty: &mut BlockType) -> Result<()> { match ty {
BlockType::Empty => Ok(()),
BlockType::Type(t) => self
.resources
.check_value_type(t, &self.features, self.offset),
BlockType::FuncType(idx) => { if !self.features.multi_value() {
bail!( self.offset, "blocks, loops, and ifs may only produce a resulttype \
when multi-value is not enabled",
);
} self.func_type_at(*idx)?;
Ok(())
}
}
}
/// Returns the corresponding function type for the `func` item located at /// `function_index`. fn type_of_function(&self, function_index: u32) -> Result<&yle='color:blue'>'resources FuncType> { iflet Some(type_index) = self.resources.type_index_of_function(function_index) { self.func_type_at(type_index)
} else {
bail!( self.offset, "unknown function {function_index}: function index out of bounds",
)
}
}
/// Checks a call-style instruction which will be invoking the function `ty` /// specified. /// /// This will pop parameters from the operand stack for the function's /// parameters and then push the results of the function on the stack. fn check_call_ty(&mutself, ty: &FuncType) -> Result<()> { for &ty in ty.params().iter().rev() {
debug_assert_type_indices_are_ids(ty); self.pop_operand(Some(ty))?;
} for &ty in ty.results() {
debug_assert_type_indices_are_ids(ty); self.push_operand(ty)?;
}
Ok(())
}
/// Similar to `check_call_ty` except used for tail-call instructions. fn check_return_call_ty(&mutself, ty: &FuncType) -> Result<()> { self.check_func_type_same_results(ty)?; for &ty in ty.params().iter().rev() {
debug_assert_type_indices_are_ids(ty); self.pop_operand(Some(ty))?;
}
// Match the results with this function's, but don't include in pop/push counts. #[cfg(debug_assertions)] let tmp = self.pop_push_count; for &ty in ty.results() {
debug_assert_type_indices_are_ids(ty); self.push_operand(ty)?;
} self.check_return()?; #[cfg(debug_assertions)]
{ self.pop_push_count = tmp;
}
Ok(())
}
/// Checks the immediate `type_index` of a `call_ref`-style instruction /// (also `return_call_ref`). /// /// This will validate that the value on the stack is a `(ref type_index)` /// or a subtype. This will then return the corresponding function type used /// for this call (to be used with `check_call_ty` or /// `check_return_call_ty`). fn check_call_ref_ty(&mutself, type_index: u32) -> Result<&yle='color:blue'>'resources FuncType> { let unpacked_index = UnpackedIndex::Module(type_index); letmut hty = HeapType::Concrete(unpacked_index); self.resources.check_heap_type(&mut hty, self.offset)?; let expected = RefType::new(true, hty).expect("hty should be previously validated"); self.pop_ref(Some(expected))?; self.func_type_at(type_index)
}
/// Validates the immediate operands of a `call_indirect` or /// `return_call_indirect` instruction. /// /// This will validate that `table_index` is valid and a funcref table. It /// will additionally pop the index argument which is used to index into the /// table. /// /// The return value of this function is the function type behind /// `type_index` which must then be passed to `check_{call,return_call}_ty`. fn check_call_indirect_ty(
&mutself,
type_index: u32,
table_index: u32,
) -> Result<&'resources FuncType> { let tab = self.table_type_at(table_index)?; if !self
.resources
.is_subtype(ValType::Ref(tab.element_type), ValType::FUNCREF)
{
bail!( self.offset, "indirect calls must go through a table with type <= funcref",
);
} self.pop_operand(Some(tab.index_type()))?; self.func_type_at(type_index)
}
/// Validates a `return` instruction, popping types from the operand /// stack that the function needs. fn check_return(&mutself) -> Result<()> { ifself.control.is_empty() { return Err(self.err_beyond_end(self.offset));
} for ty inself.results(self.control[0].block_type)?.rev() { self.pop_operand(Some(ty))?;
} self.unreachable()?;
Ok(())
}
/// Check that the given type has the same result types as the current /// function's results. fn check_func_type_same_results(&self, callee_ty: &FuncType) -> Result<()> { ifself.control.is_empty() { return Err(self.err_beyond_end(self.offset));
} let caller_rets = self.results(self.control[0].block_type)?; if callee_ty.results().len() != caller_rets.len()
|| !caller_rets
.zip(callee_ty.results())
.all(|(caller_ty, callee_ty)| self.resources.is_subtype(*callee_ty, caller_ty))
{ let caller_rets = self
.results(self.control[0].block_type)?
.map(|ty| format!("{ty}"))
.collect::<Vec<_>>()
.join(" "); let callee_rets = callee_ty
.results()
.iter()
.map(|ty| format!("{ty}"))
.collect::<Vec<_>>()
.join(" ");
bail!( self.offset, "type mismatch: current function requires result type \
[{caller_rets}] but callee returns [{callee_rets}]"
);
}
Ok(())
}
/// Checks the validity of a common comparison operator. fn check_cmp_op(&mutself, ty: ValType) -> Result<()> { self.pop_operand(Some(ty))?; self.pop_operand(Some(ty))?; self.push_operand(ValType::I32)?;
Ok(())
}
/// Checks the validity of a common float comparison operator. fn check_fcmp_op(&mutself, ty: ValType) -> Result<()> {
debug_assert!(matches!(ty, ValType::F32 | ValType::F64)); self.check_floats_enabled()?; self.check_cmp_op(ty)
}
/// Checks the validity of a common unary operator. fn check_unary_op(&mutself, ty: ValType) -> Result<()> { self.pop_operand(Some(ty))?; self.push_operand(ty)?;
Ok(())
}
/// Checks the validity of a common unary float operator. fn check_funary_op(&mutself, ty: ValType) -> Result<()> {
debug_assert!(matches!(ty, ValType::F32 | ValType::F64)); self.check_floats_enabled()?; self.check_unary_op(ty)
}
/// Checks the validity of a common conversion operator. fn check_conversion_op(&mutself, into: ValType, from: ValType) -> Result<()> { self.pop_operand(Some(from))?; self.push_operand(into)?;
Ok(())
}
/// Checks the validity of a common float conversion operator. fn check_fconversion_op(&mutself, into: ValType, from: ValType) -> Result<()> {
debug_assert!(matches!(into, ValType::F32 | ValType::F64)); self.check_floats_enabled()?; self.check_conversion_op(into, from)
}
/// Checks the validity of a common binary operator. fn check_binary_op(&mutself, ty: ValType) -> Result<()> { self.pop_operand(Some(ty))?; self.pop_operand(Some(ty))?; self.push_operand(ty)?;
Ok(())
}
/// Checks the validity of a common binary float operator. fn check_fbinary_op(&mutself, ty: ValType) -> Result<()> {
debug_assert!(matches!(ty, ValType::F32 | ValType::F64)); self.check_floats_enabled()?; self.check_binary_op(ty)
}
/// Checks the validity of an atomic load operator. fn check_atomic_load(&mutself, memarg: MemArg, load_ty: ValType) -> Result<()> { let ty = self.check_shared_memarg(memarg)?; self.pop_operand(Some(ty))?; self.push_operand(load_ty)?;
Ok(())
}
/// Checks the validity of an atomic store operator. fn check_atomic_store(&mutself, memarg: MemArg, store_ty: ValType) -> Result<()> { let ty = self.check_shared_memarg(memarg)?; self.pop_operand(Some(store_ty))?; self.pop_operand(Some(ty))?;
Ok(())
}
/// Checks the validity of atomic binary operator on memory. fn check_atomic_binary_memory_op(&mutself, memarg: MemArg, op_ty: ValType) -> Result<()> { let ty = self.check_shared_memarg(memarg)?; self.pop_operand(Some(op_ty))?; self.pop_operand(Some(ty))?; self.push_operand(op_ty)?;
Ok(())
}
/// Checks the validity of an atomic compare exchange operator on memories. fn check_atomic_binary_memory_cmpxchg(&mutself, memarg: MemArg, op_ty: ValType) -> Result<()> { let ty = self.check_shared_memarg(memarg)?; self.pop_operand(Some(op_ty))?; self.pop_operand(Some(op_ty))?; self.pop_operand(Some(ty))?; self.push_operand(op_ty)?;
Ok(())
}
/// Checks a [`V128`] common load operator. fn check_v128_load_op(&mutself, memarg: MemArg) -> Result<()> { let idx = self.check_memarg(memarg)?; self.pop_operand(Some(idx))?; self.push_operand(ValType::V128)?;
Ok(())
}
/// Common helper for `ref.test` and `ref.cast` downcasting/checking /// instructions. Returns the given `heap_type` as a `ValType`. fn check_downcast(&mutself, nullable: bool, mut heap_type: HeapType) -> Result<RefType> { self.resources
.check_heap_type(&mut heap_type, self.offset)?;
let sub_ty = RefType::new(nullable, heap_type).ok_or_else(|| {
BinaryReaderError::new("implementation limit: type index too large", self.offset)
})?; let sup_ty = RefType::new(true, self.resources.top_type(&heap_type))
.expect("can't panic with non-concrete heap types");
self.pop_ref(Some(sup_ty))?;
Ok(sub_ty)
}
/// Common helper for both nullable and non-nullable variants of `ref.test` /// instructions. fn check_ref_test(&mutself, nullable: bool, heap_type: HeapType) -> Result<()> { self.check_downcast(nullable, heap_type)?; self.push_operand(ValType::I32)
}
/// Common helper for both nullable and non-nullable variants of `ref.cast` /// instructions. fn check_ref_cast(&mutself, nullable: bool, heap_type: HeapType) -> Result<()> { let sub_ty = self.check_downcast(nullable, heap_type)?; self.push_operand(sub_ty)
}
/// Common helper for checking the types of globals accessed with atomic RMW /// instructions, which only allow `i32` and `i64`. fn check_atomic_global_rmw_ty(&self, global_index: u32) -> Result<ValType> { let ty = self.global_type_at(global_index)?.content_type; if !(ty == ValType::I32 || ty == ValType::I64) {
bail!( self.offset, "invalid type: `global.atomic.rmw.*` only allows `i32` and `i64`"
);
}
Ok(ty)
}
/// Common helper for checking the types of structs accessed with atomic RMW /// instructions, which only allow `i32` and `i64` types. fn check_struct_atomic_rmw(
&mutself,
op: &'static str,
struct_type_index: u32,
field_index: u32,
) -> Result<()> { let field = self.mutable_struct_field_at(struct_type_index, field_index)?; let field_ty = match field.element_type {
StorageType::Val(ValType::I32) => ValType::I32,
StorageType::Val(ValType::I64) => ValType::I64,
_ => bail!( self.offset, "invalid type: `struct.atomic.rmw.{}` only allows `i32` and `i64`",
op
),
}; self.pop_operand(Some(field_ty))?; self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty)?;
Ok(())
}
/// Common helper for checking the types of arrays accessed with atomic RMW /// instructions, which only allow `i32` and `i64`. fn check_array_atomic_rmw(&mutself, op: &'static str, type_index: u32) -> Result<()> { let field = self.mutable_array_type_at(type_index)?; let elem_ty = match field.element_type {
StorageType::Val(ValType::I32) => ValType::I32,
StorageType::Val(ValType::I64) => ValType::I64,
_ => bail!( self.offset, "invalid type: `array.atomic.rmw.{}` only allows `i32` and `i64`",
op
),
}; self.pop_operand(Some(elem_ty))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty)?;
Ok(())
}
fn element_type_at(&self, elem_index: u32) -> Result<RefType> { matchself.resources.element_type_at(elem_index) {
Some(ty) => Ok(ty),
None => bail!( self.offset, "unknown elem segment {}: segment index out of bounds",
elem_index
),
}
}
fn sub_type_at(&self, at: u32) -> Result<&'resources SubType> { self.resources
.sub_type_at(at)
.ok_or_else(|| format_err!(self.offset, "unknown type: type index out of bounds"))
}
fn struct_type_at(&self, at: u32) -> Result<&'resources StructType> { let sub_ty = self.sub_type_at(at)?; iflet CompositeInnerType::Struct(struct_ty) = &sub_ty.composite_type.inner { ifself.inner.shared && !sub_ty.composite_type.shared {
bail!( self.offset, "shared functions cannot access unshared structs",
);
}
Ok(struct_ty)
} else {
bail!( self.offset, "expected struct type at index {at}, found {sub_ty}"
)
}
}
fn struct_field_at(&self, struct_type_index: u32, field_index: u32) -> Result<FieldType> { let field_index = usize::try_from(field_index).map_err(|_| {
BinaryReaderError::new("unknown field: field index out of bounds", self.offset)
})?; self.struct_type_at(struct_type_index)?
.fields
.get(field_index)
.copied()
.ok_or_else(|| {
BinaryReaderError::new("unknown field: field index out of bounds", self.offset)
})
}
fn mutable_struct_field_at(
&self,
struct_type_index: u32,
field_index: u32,
) -> Result<FieldType> { let field = self.struct_field_at(struct_type_index, field_index)?; if !field.mutable {
bail!( self.offset, "invalid struct modification: struct field is immutable"
)
}
Ok(field)
}
fn array_type_at(&self, at: u32) -> Result<FieldType> { let sub_ty = self.sub_type_at(at)?; iflet CompositeInnerType::Array(array_ty) = &sub_ty.composite_type.inner { ifself.inner.shared && !sub_ty.composite_type.shared {
bail!( self.offset, "shared functions cannot access unshared arrays",
);
}
Ok(array_ty.0)
} else {
bail!( self.offset, "expected array type at index {at}, found {sub_ty}"
)
}
}
fn mutable_array_type_at(&self, at: u32) -> Result<FieldType> { let field = self.array_type_at(at)?; if !field.mutable {
bail!( self.offset, "invalid array modification: array is immutable"
)
}
Ok(field)
}
fn func_type_at(&self, at: u32) -> Result<&>'resources FuncType> { let sub_ty = self.sub_type_at(at)?; iflet CompositeInnerType::Func(func_ty) = &sub_ty.composite_type.inner { ifself.inner.shared && !sub_ty.composite_type.shared {
bail!( self.offset, "shared functions cannot access unshared functions",
);
}
Ok(func_ty)
} else {
bail!( self.offset, "expected func type at index {at}, found {sub_ty}"
)
}
}
fn func_type_of_cont_type(&self, cont_ty: &ContType) -> &'resources FuncType { let func_id = cont_ty.0.as_core_type_id().expect("valid core type id"); self.resources.sub_type_at_id(func_id).unwrap_func()
}
fn tag_at(&self, at: u32) -> Result<&'resources FuncType> { self.resources
.tag_at(at)
.ok_or_else(|| format_err!(self.offset, "unknown tag {}: tag index out of bounds", at))
}
// Similar to `tag_at`, but checks that the result type is // empty. This is necessary when enabling the stack switching // feature as it allows non-empty result types on tags. fn exception_tag_at(&self, at: u32) -> Result<&'resources FuncType> { let func_ty = self.tag_at(at)?; if func_ty.results().len() != 0 {
bail!( self.offset, "invalid exception type: non-empty tag result type"
);
}
Ok(func_ty)
}
fn global_type_at(&self, at: u32) -> Result<GlobalType> { iflet Some(ty) = self.resources.global_at(at) { ifself.inner.shared && !ty.shared {
bail!( self.offset, "shared functions cannot access unshared globals",
);
}
Ok(ty)
} else {
bail!(self.offset, "unknown global: global index out of bounds");
}
}
/// Validates that the `table` is valid and returns the type it points to. fn table_type_at(&self, table: u32) -> Result<TableType> { matchself.resources.table_at(table) {
Some(ty) => { ifself.inner.shared && !ty.shared {
bail!( self.offset, "shared functions cannot access unshared tables",
);
}
Ok(ty)
}
None => bail!( self.offset, "unknown table {table}: table index out of bounds"
),
}
}
/// A wrapper "visitor" around the real operator validator internally which /// exists to check that the required wasm feature is enabled to proceed with /// validation. /// /// This validator is macro-generated to ensure that the proposal listed in this /// crate's macro matches the one that's validated here. Each instruction's /// visit method validates the specified proposal is enabled and then delegates /// to `OperatorValidatorTemp` to perform the actual opcode validation. struct WasmProposalValidator<'validator, 'resources, T>(
OperatorValidatorTemp<'validator, 'resources, T>,
);
impl<T> WasmProposalValidator<'_, '_, T> { fn check_enabled(&self, flag: bool, desc: &str) -> Result<()> { if flag { return Ok(());
}
bail!(self.0.offset, "{desc} support is not enabled");
}
}
impl<'a, T> VisitOperator<'a> for WasmProposalValidator<'_, '_, T> where
T: WasmModuleResources,
{ type Output = Result<()>;
for_each_operator!(validate_proposal);
}
#[track_caller] #[inline] fn debug_assert_type_indices_are_ids(ty: ValType) { if cfg!(debug_assertions) { iflet ValType::Ref(r) = ty { iflet HeapType::Concrete(idx) = r.heap_type() {
debug_assert!(
matches!(idx, UnpackedIndex::Id(_)), "type reference should be a `CoreTypeId`, found {idx:?}"
);
}
}
}
}
impl<'a, T> VisitOperator<'a> for OperatorValidatorTemp<'_, '_, T> where
T: WasmModuleResources,
{ type Output = Result<()>;
fn visit_nop(&mutself) -> Self::Output {
Ok(())
} fn visit_unreachable(&mutself) -> Self::Output { self.unreachable()?;
Ok(())
} fn visit_block(&mutself, mut ty: BlockType) -> Self::Output { self.check_block_type(&mut ty)?; for ty inself.params(ty)?.rev() { self.pop_operand(Some(ty))?;
} self.push_ctrl(FrameKind::Block, ty)?;
Ok(())
} fn visit_loop(&mutself, mut ty: BlockType) -> Self::Output { self.check_block_type(&mut ty)?; for ty inself.params(ty)?.rev() { self.pop_operand(Some(ty))?;
} self.push_ctrl(FrameKind::Loop, ty)?;
Ok(())
} fn visit_if(&mutself, mut ty: BlockType) -> Self::Output { self.check_block_type(&mut ty)?; self.pop_operand(Some(ValType::I32))?; for ty inself.params(ty)?.rev() { self.pop_operand(Some(ty))?;
} self.push_ctrl(FrameKind::If, ty)?;
Ok(())
} fn visit_else(&mutself) -> Self::Output { let frame = self.pop_ctrl()?; if frame.kind != FrameKind::If {
bail!(self.offset, "else found outside of an `if` block");
} self.push_ctrl(FrameKind::Else, frame.block_type)?;
Ok(())
} fn visit_try_table(&mutself, mut ty: TryTable) -> Self::Output { self.check_block_type(&mut ty.ty)?; for ty inself.params(ty.ty)?.rev() { self.pop_operand(Some(ty))?;
} let exn_type = ValType::from(RefType::EXN); for catch in ty.catches { match catch {
Catch::One { tag, label } => { let tag = self.exception_tag_at(tag)?; let (ty, kind) = self.jump(label)?; let params = tag.params(); let types = self.label_types(ty, kind)?; if params.len() != types.len() {
bail!( self.offset, "type mismatch: catch label must have same number of types as tag"
);
} for (expected, actual) in types.zip(params) { self.match_operand(*actual, expected)?;
}
}
Catch::OneRef { tag, label } => { let tag = self.exception_tag_at(tag)?; let (ty, kind) = self.jump(label)?; let tag_params = tag.params().iter().copied(); let label_types = self.label_types(ty, kind)?; if tag_params.len() + 1 != label_types.len() {
bail!( self.offset, "type mismatch: catch_ref label must have one \
more type than tag types",
);
} for (expected_label_type, actual_tag_param) in
label_types.zip(tag_params.chain([exn_type]))
{ self.match_operand(actual_tag_param, expected_label_type)?;
}
}
Catch::All { label } => { let (ty, kind) = self.jump(label)?; ifself.label_types(ty, kind)?.len() != 0 {
bail!( self.offset, "type mismatch: catch_all label must have no result types"
);
}
}
Catch::AllRef { label } => { let (ty, kind) = self.jump(label)?; letmut types = self.label_types(ty, kind)?; let ty = match (types.next(), types.next()) {
(Some(ty), None) => ty,
_ => {
bail!( self.offset, "type mismatch: catch_all_ref label must have \
exactly one result type"
);
}
}; if !self.resources.is_subtype(exn_type, ty) {
bail!( self.offset, "type mismatch: catch_all_ref label must a \
subtype of (ref exn)"
);
}
}
}
} self.push_ctrl(FrameKind::TryTable, ty.ty)?;
Ok(())
} fn visit_throw(&mutself, index: u32) -> Self::Output { // Check values associated with the exception. let ty = self.exception_tag_at(index)?; for ty in ty.clone().params().iter().rev() { self.pop_operand(Some(*ty))?;
} // this should be validated when the tag was defined in the module
debug_assert!(ty.results().is_empty()); self.unreachable()?;
Ok(())
} fn visit_throw_ref(&mutself) -> Self::Output { self.pop_operand(Some(ValType::EXNREF))?; self.unreachable()?;
Ok(())
} fn visit_end(&mutself) -> Self::Output { letmut frame = self.pop_ctrl()?;
// Note that this `if` isn't included in the appendix right // now, but it's used to allow for `if` statements that are // missing an `else` block which have the same parameter/return // types on the block (since that's valid). if frame.kind == FrameKind::If { self.push_ctrl(FrameKind::Else, frame.block_type)?;
frame = self.pop_ctrl()?;
} for ty inself.results(frame.block_type)? { self.push_operand(ty)?;
}
ifself.control.is_empty() && self.end_which_emptied_control.is_none() {
assert_ne!(self.offset, 0); self.end_which_emptied_control = Some(self.offset);
}
Ok(())
} fn visit_br(&mutself, relative_depth: u32) -> Self::Output { let (ty, kind) = self.jump(relative_depth)?; for ty inself.label_types(ty, kind)?.rev() { self.pop_operand(Some(ty))?;
} self.unreachable()?;
Ok(())
} fn visit_br_if(&mutself, relative_depth: u32) -> Self::Output { self.pop_operand(Some(ValType::I32))?; let (ty, kind) = self.jump(relative_depth)?; let label_types = self.label_types(ty, kind)?; self.pop_push_label_types(label_types)?;
Ok(())
} fn visit_br_table(&mutself, table: BrTable) -> Self::Output { self.pop_operand(Some(ValType::I32))?; let default = self.jump(table.default())?; let default_types = self.label_types(default.0, default.1)?; for element in table.targets() { let relative_depth = element?; let block = self.jump(relative_depth)?; let label_tys = self.label_types(block.0, block.1)?; if label_tys.len() != default_types.len() {
bail!( self.offset, "type mismatch: br_table target labels have different number of types"
);
} self.match_stack_operands(label_tys)?;
} for ty in default_types.rev() { self.pop_operand(Some(ty))?;
} self.unreachable()?;
Ok(())
} fn visit_return(&mutself) -> Self::Output { self.check_return()?;
Ok(())
} fn visit_call(&mutself, function_index: u32) -> Self::Output { let ty = self.type_of_function(function_index)?; self.check_call_ty(ty)?;
Ok(())
} fn visit_return_call(&mutself, function_index: u32) -> Self::Output { let ty = self.type_of_function(function_index)?; self.check_return_call_ty(ty)?;
Ok(())
} fn visit_call_ref(&mutself, type_index: u32) -> Self::Output { let ty = self.check_call_ref_ty(type_index)?; self.check_call_ty(ty)?;
Ok(())
} fn visit_return_call_ref(&mutself, type_index: u32) -> Self::Output { let ty = self.check_call_ref_ty(type_index)?; self.check_return_call_ty(ty)?;
Ok(())
} fn visit_call_indirect(&mutself, type_index: u32, table_index: u32) -> Self::Output { let ty = self.check_call_indirect_ty(type_index, table_index)?; self.check_call_ty(ty)?;
Ok(())
} fn visit_return_call_indirect(&mutself, type_index: u32, table_index: u32) -> Self::Output { let ty = self.check_call_indirect_ty(type_index, table_index)?; self.check_return_call_ty(ty)?;
Ok(())
} fn visit_drop(&mutself) -> Self::Output { self.pop_operand(None)?;
Ok(())
} fn visit_select(&mutself) -> Self::Output { self.pop_operand(Some(ValType::I32))?; let ty1 = self.pop_operand(None)?; let ty2 = self.pop_operand(None)?;
let ty = match (ty1, ty2) { // All heap-related types aren't allowed with the `select` // instruction
(MaybeType::UnknownRef(..), _)
| (_, MaybeType::UnknownRef(..))
| (MaybeType::Known(ValType::Ref(_)), _)
| (_, MaybeType::Known(ValType::Ref(_))) => {
bail!( self.offset, "type mismatch: select only takes integral types"
)
}
// If one operand is the "bottom" type then whatever the other // operand is is the result of the `select`
(MaybeType::Bottom, t) | (t, MaybeType::Bottom) => t,
// Otherwise these are two integral types and they must match for // `select` to typecheck.
(t @ MaybeType::Known(t1), MaybeType::Known(t2)) => { if t1 != t2 {
bail!( self.offset, "type mismatch: select operands have different types"
);
}
t
}
}; self.push_operand(ty)?;
Ok(())
} fn visit_typed_select(&mutself, mut ty: ValType) -> Self::Output { self.resources
.check_value_type(&mut ty, &self.features, self.offset)?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ty))?; self.pop_operand(Some(ty))?; self.push_operand(ty)?;
Ok(())
} fn visit_local_get(&mutself, local_index: u32) -> Self::Output { let ty = self.local(local_index)?;
debug_assert_type_indices_are_ids(ty); if !self.local_inits[local_index as usize] {
bail!(self.offset, "uninitialized local: {}", local_index);
} self.push_operand(ty)?;
Ok(())
} fn visit_local_set(&mutself, local_index: u32) -> Self::Output { let ty = self.local(local_index)?; self.pop_operand(Some(ty))?; if !self.local_inits[local_index as usize] { self.local_inits[local_index as usize] = true; self.inits.push(local_index);
}
Ok(())
} fn visit_local_tee(&mutself, local_index: u32) -> Self::Output { let expected_ty = self.local(local_index)?; self.pop_operand(Some(expected_ty))?; if !self.local_inits[local_index as usize] { self.local_inits[local_index as usize] = true; self.inits.push(local_index);
}
self.push_operand(expected_ty)?;
Ok(())
} fn visit_global_get(&mutself, global_index: u32) -> Self::Output { let ty = self.global_type_at(global_index)?.content_type;
debug_assert_type_indices_are_ids(ty); self.push_operand(ty)?;
Ok(())
} fn visit_global_atomic_get(&mutself, _ordering: Ordering, global_index: u32) -> Self::Output { self.visit_global_get(global_index)?; // No validation of `ordering` is needed because `global.atomic.get` can // be used on both shared and unshared globals. But we do need to limit // which types can be used with this instruction. let ty = self.global_type_at(global_index)?.content_type; let supertype = RefType::ANYREF.into(); if !(ty == ValType::I32 || ty == ValType::I64 || self.resources.is_subtype(ty, supertype)) {
bail!(self.offset, "invalid type: `global.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`");
}
Ok(())
} fn visit_global_set(&mutself, global_index: u32) -> Self::Output { let ty = self.global_type_at(global_index)?; if !ty.mutable {
bail!( self.offset, "global is immutable: cannot modify it with `global.set`"
);
} self.pop_operand(Some(ty.content_type))?;
Ok(())
} fn visit_global_atomic_set(&mutself, _ordering: Ordering, global_index: u32) -> Self::Output { self.visit_global_set(global_index)?; // No validation of `ordering` is needed because `global.atomic.get` can // be used on both shared and unshared globals. let ty = self.global_type_at(global_index)?.content_type; let supertype = RefType::ANYREF.into(); if !(ty == ValType::I32 || ty == ValType::I64 || self.resources.is_subtype(ty, supertype)) {
bail!(self.offset, "invalid type: `global.atomic.set` only allows `i32`, `i64` and subtypes of `anyref`");
}
Ok(())
} fn visit_global_atomic_rmw_add(
&mutself,
_ordering: crate::Ordering,
global_index: u32,
) -> Self::Output { let ty = self.check_atomic_global_rmw_ty(global_index)?; self.check_unary_op(ty)
} fn visit_global_atomic_rmw_sub(
&mutself,
_ordering: crate::Ordering,
global_index: u32,
) -> Self::Output { let ty = self.check_atomic_global_rmw_ty(global_index)?; self.check_unary_op(ty)
} fn visit_global_atomic_rmw_and(
&mutself,
_ordering: crate::Ordering,
global_index: u32,
) -> Self::Output { let ty = self.check_atomic_global_rmw_ty(global_index)?; self.check_unary_op(ty)
} fn visit_global_atomic_rmw_or(
&mutself,
_ordering: crate::Ordering,
global_index: u32,
) -> Self::Output { let ty = self.check_atomic_global_rmw_ty(global_index)?; self.check_unary_op(ty)
} fn visit_global_atomic_rmw_xor(
&mutself,
_ordering: crate::Ordering,
global_index: u32,
) -> Self::Output { let ty = self.check_atomic_global_rmw_ty(global_index)?; self.check_unary_op(ty)
} fn visit_global_atomic_rmw_xchg(
&mutself,
_ordering: crate::Ordering,
global_index: u32,
) -> Self::Output { let ty = self.global_type_at(global_index)?.content_type; if !(ty == ValType::I32
|| ty == ValType::I64
|| self.resources.is_subtype(ty, RefType::ANYREF.into()))
{
bail!(self.offset, "invalid type: `global.atomic.rmw.xchg` only allows `i32`, `i64` and subtypes of `anyref`");
} self.check_unary_op(ty)
} fn visit_global_atomic_rmw_cmpxchg(
&mutself,
_ordering: crate::Ordering,
global_index: u32,
) -> Self::Output { let ty = self.global_type_at(global_index)?.content_type; if !(ty == ValType::I32
|| ty == ValType::I64
|| self.resources.is_subtype(ty, RefType::EQREF.into()))
{
bail!(self.offset, "invalid type: `global.atomic.rmw.cmpxchg` only allows `i32`, `i64` and subtypes of `eqref`");
} self.check_binary_op(ty)
}
fn visit_ref_as_non_null(&mutself) -> Self::Output { let ty = self.pop_ref(None)?.as_non_null(); self.push_operand(ty)?;
Ok(())
} fn visit_br_on_null(&mutself, relative_depth: u32) -> Self::Output { let ref_ty = self.pop_ref(None)?.as_non_null(); let (ft, kind) = self.jump(relative_depth)?; let label_types = self.label_types(ft, kind)?; self.pop_push_label_types(label_types)?; self.push_operand(ref_ty)?;
Ok(())
} fn visit_br_on_non_null(&mutself, relative_depth: u32) -> Self::Output { let (ft, kind) = self.jump(relative_depth)?;
letmut label_types = self.label_types(ft, kind)?; let expected = match label_types.next_back() {
None => bail!( self.offset, "type mismatch: br_on_non_null target has no label types",
),
Some(ValType::Ref(ty)) => ty,
Some(_) => bail!( self.offset, "type mismatch: br_on_non_null target does not end with heap type",
),
}; self.pop_ref(Some(expected.nullable()))?;
self.pop_push_label_types(label_types)?;
Ok(())
} fn visit_ref_is_null(&mutself) -> Self::Output { self.pop_ref(None)?; self.push_operand(ValType::I32)?;
Ok(())
} fn visit_ref_func(&mutself, function_index: u32) -> Self::Output { let type_id = matchself.resources.type_id_of_function(function_index) {
Some(id) => id,
None => bail!( self.offset, "unknown function {}: function index out of bounds",
function_index,
),
}; if !self.resources.is_function_referenced(function_index) {
bail!(self.offset, "undeclared function reference");
}
let index = UnpackedIndex::Id(type_id); let ty = ValType::Ref(
RefType::new(false, HeapType::Concrete(index)).ok_or_else(|| {
BinaryReaderError::new("implementation limit: type index too large", self.offset)
})?,
); self.push_operand(ty)?;
Ok(())
} fn visit_ref_eq(&mutself) -> Self::Output { let a = self.pop_maybe_shared_ref(AbstractHeapType::Eq)?; let b = self.pop_maybe_shared_ref(AbstractHeapType::Eq)?; let a_is_shared = a.is_maybe_shared(&self.resources); let b_is_shared = b.is_maybe_shared(&self.resources); match (a_is_shared, b_is_shared) { // One or both of the types are from unreachable code; assume // the shared-ness matches.
(None, Some(_)) | (Some(_), None) | (None, None) => {}
// The length operand here is the smaller of src/dst, which is // i32 if one is i32 self.pop_operand(Some(match src_ty {
ValType::I32 => ValType::I32,
_ => dst_ty,
}))?;
// ... and the offset into each memory is required to be // whatever the indexing type is for that memory self.pop_operand(Some(src_ty))?; self.pop_operand(Some(dst_ty))?;
Ok(())
} fn visit_memory_fill(&mutself, mem: u32) -> Self::Output { let ty = self.check_memory_index(mem)?; self.pop_operand(Some(ty))?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ty))?;
Ok(())
} fn visit_memory_discard(&mutself, mem: u32) -> Self::Output { let ty = self.check_memory_index(mem)?; self.pop_operand(Some(ty))?; self.pop_operand(Some(ty))?;
Ok(())
} fn visit_table_init(&mutself, segment: u32, table: u32) -> Self::Output { let table = self.table_type_at(table)?; let segment_ty = self.element_type_at(segment)?; if !self
.resources
.is_subtype(ValType::Ref(segment_ty), ValType::Ref(table.element_type))
{
bail!(self.offset, "type mismatch");
} self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(table.index_type()))?;
Ok(())
} fn visit_elem_drop(&mutself, segment: u32) -> Self::Output { self.element_type_at(segment)?;
Ok(())
} fn visit_table_copy(&mutself, dst_table: u32, src_table: u32) -> Self::Output { let src = self.table_type_at(src_table)?; let dst = self.table_type_at(dst_table)?; if !self.resources.is_subtype(
ValType::Ref(src.element_type),
ValType::Ref(dst.element_type),
) {
bail!(self.offset, "type mismatch");
}
// The length operand here is the smaller of src/dst, which is // i32 if one is i32 self.pop_operand(Some(match src.index_type() {
ValType::I32 => ValType::I32,
_ => dst.index_type(),
}))?;
// ... and the offset into each table is required to be // whatever the indexing type is for that table self.pop_operand(Some(src.index_type()))?; self.pop_operand(Some(dst.index_type()))?;
Ok(())
} fn visit_table_get(&mutself, table: u32) -> Self::Output { let table = self.table_type_at(table)?;
debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.index_type()))?; self.push_operand(table.element_type)?;
Ok(())
} fn visit_table_atomic_get(&mutself, _ordering: Ordering, table: u32) -> Self::Output { self.visit_table_get(table)?; // No validation of `ordering` is needed because `table.atomic.get` can // be used on both shared and unshared tables. But we do need to limit // which types can be used with this instruction. let ty = self.table_type_at(table)?.element_type; let supertype = RefType::ANYREF.shared().unwrap(); if !self.resources.is_subtype(ty.into(), supertype.into()) {
bail!( self.offset, "invalid type: `table.atomic.get` only allows subtypes of `anyref`"
);
}
Ok(())
} fn visit_table_set(&mutself, table: u32) -> Self::Output { let table = self.table_type_at(table)?;
debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.element_type.into()))?; self.pop_operand(Some(table.index_type()))?;
Ok(())
} fn visit_table_atomic_set(&mutself, _ordering: Ordering, table: u32) -> Self::Output { self.visit_table_set(table)?; // No validation of `ordering` is needed because `table.atomic.set` can // be used on both shared and unshared tables. But we do need to limit // which types can be used with this instruction. let ty = self.table_type_at(table)?.element_type; let supertype = RefType::ANYREF.shared().unwrap(); if !self.resources.is_subtype(ty.into(), supertype.into()) {
bail!( self.offset, "invalid type: `table.atomic.set` only allows subtypes of `anyref`"
);
}
Ok(())
} fn visit_table_grow(&mutself, table: u32) -> Self::Output { let table = self.table_type_at(table)?;
debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.index_type()))?; self.pop_operand(Some(table.element_type.into()))?; self.push_operand(table.index_type())?;
Ok(())
} fn visit_table_size(&mutself, table: u32) -> Self::Output { let table = self.table_type_at(table)?; self.push_operand(table.index_type())?;
Ok(())
} fn visit_table_fill(&mutself, table: u32) -> Self::Output { let table = self.table_type_at(table)?;
debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.index_type()))?; self.pop_operand(Some(table.element_type.into()))?; self.pop_operand(Some(table.index_type()))?;
Ok(())
} fn visit_table_atomic_rmw_xchg(&mutself, _ordering: Ordering, table: u32) -> Self::Output { let table = self.table_type_at(table)?; let elem_ty = table.element_type.into();
debug_assert_type_indices_are_ids(elem_ty); let supertype = RefType::ANYREF.shared().unwrap(); if !self.resources.is_subtype(elem_ty, supertype.into()) {
bail!( self.offset, "invalid type: `table.atomic.rmw.xchg` only allows subtypes of `anyref`"
);
} self.pop_operand(Some(elem_ty))?; self.pop_operand(Some(table.index_type()))?; self.push_operand(elem_ty)?;
Ok(())
} fn visit_table_atomic_rmw_cmpxchg(&mutself, _ordering: Ordering, table: u32) -> Self::Output { let table = self.table_type_at(table)?; let elem_ty = table.element_type.into();
debug_assert_type_indices_are_ids(elem_ty); let supertype = RefType::EQREF.shared().unwrap(); if !self.resources.is_subtype(elem_ty, supertype.into()) {
bail!( self.offset, "invalid type: `table.atomic.rmw.cmpxchg` only allows subtypes of `eqref`"
);
} self.pop_operand(Some(elem_ty))?; self.pop_operand(Some(elem_ty))?; self.pop_operand(Some(table.index_type()))?; self.push_operand(elem_ty)?;
Ok(())
} fn visit_struct_new(&mutself, struct_type_index: u32) -> Self::Output { let struct_ty = self.struct_type_at(struct_type_index)?; for ty in struct_ty.fields.iter().rev() { self.pop_operand(Some(ty.element_type.unpack()))?;
} self.push_concrete_ref(false, struct_type_index)?;
Ok(())
} fn visit_struct_new_default(&mutself, type_index: u32) -> Self::Output { let ty = self.struct_type_at(type_index)?; for field in ty.fields.iter() { let val_ty = field.element_type.unpack(); if !val_ty.is_defaultable() {
bail!( self.offset, "invalid `struct.new_default`: {val_ty} field is not defaultable"
);
}
} self.push_concrete_ref(false, type_index)?;
Ok(())
} fn visit_struct_get(&mutself, struct_type_index: u32, field_index: u32) -> Self::Output { let field_ty = self.struct_field_at(struct_type_index, field_index)?; if field_ty.element_type.is_packed() {
bail!( self.offset, "can only use struct `get` with non-packed storage types"
)
} self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty.element_type.unpack())
} fn visit_struct_atomic_get(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.visit_struct_get(struct_type_index, field_index)?; // The `atomic` version has some additional type restrictions. let ty = self
.struct_field_at(struct_type_index, field_index)?
.element_type; let is_valid_type = match ty {
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::ANYREF.shared().unwrap().into()),
_ => false,
}; if !is_valid_type {
bail!( self.offset, "invalid type: `struct.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`"
);
}
Ok(())
} fn visit_struct_get_s(&mutself, struct_type_index: u32, field_index: u32) -> Self::Output { let field_ty = self.struct_field_at(struct_type_index, field_index)?; if !field_ty.element_type.is_packed() {
bail!( self.offset, "cannot use struct.get_s with non-packed storage types"
)
} self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty.element_type.unpack())
} fn visit_struct_atomic_get_s(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.visit_struct_get_s(struct_type_index, field_index)?; // This instruction has the same type restrictions as the non-`atomic` version.
debug_assert!(matches!( self.struct_field_at(struct_type_index, field_index)?
.element_type,
StorageType::I8 | StorageType::I16
));
Ok(())
} fn visit_struct_get_u(&mutself, struct_type_index: u32, field_index: u32) -> Self::Output { let field_ty = self.struct_field_at(struct_type_index, field_index)?; if !field_ty.element_type.is_packed() {
bail!( self.offset, "cannot use struct.get_u with non-packed storage types"
)
} self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty.element_type.unpack())
} fn visit_struct_atomic_get_u(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.visit_struct_get_s(struct_type_index, field_index)?; // This instruction has the same type restrictions as the non-`atomic` version.
debug_assert!(matches!( self.struct_field_at(struct_type_index, field_index)?
.element_type,
StorageType::I8 | StorageType::I16
));
Ok(())
} fn visit_struct_set(&mutself, struct_type_index: u32, field_index: u32) -> Self::Output { let field_ty = self.mutable_struct_field_at(struct_type_index, field_index)?; self.pop_operand(Some(field_ty.element_type.unpack()))?; self.pop_concrete_ref(true, struct_type_index)?;
Ok(())
} fn visit_struct_atomic_set(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.visit_struct_set(struct_type_index, field_index)?; // The `atomic` version has some additional type restrictions. let ty = self
.struct_field_at(struct_type_index, field_index)?
.element_type; let is_valid_type = match ty {
StorageType::I8 | StorageType::I16 => true,
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::ANYREF.shared().unwrap().into()),
}; if !is_valid_type {
bail!( self.offset, "invalid type: `struct.atomic.set` only allows `i8`, `i16`, `i32`, `i64` and subtypes of `anyref`"
);
}
Ok(())
} fn visit_struct_atomic_rmw_add(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.check_struct_atomic_rmw("add", struct_type_index, field_index)
} fn visit_struct_atomic_rmw_sub(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.check_struct_atomic_rmw("sub", struct_type_index, field_index)
} fn visit_struct_atomic_rmw_and(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.check_struct_atomic_rmw("and", struct_type_index, field_index)
} fn visit_struct_atomic_rmw_or(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.check_struct_atomic_rmw("or", struct_type_index, field_index)
} fn visit_struct_atomic_rmw_xor(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { self.check_struct_atomic_rmw("xor", struct_type_index, field_index)
} fn visit_struct_atomic_rmw_xchg(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { let field = self.mutable_struct_field_at(struct_type_index, field_index)?; let is_valid_type = match field.element_type {
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::ANYREF.shared().unwrap().into()),
_ => false,
}; if !is_valid_type {
bail!( self.offset, "invalid type: `struct.atomic.rmw.xchg` only allows `i32`, `i64` and subtypes of `anyref`"
);
} let field_ty = field.element_type.unpack(); self.pop_operand(Some(field_ty))?; self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty)?;
Ok(())
} fn visit_struct_atomic_rmw_cmpxchg(
&mutself,
_ordering: Ordering,
struct_type_index: u32,
field_index: u32,
) -> Self::Output { let field = self.mutable_struct_field_at(struct_type_index, field_index)?; let is_valid_type = match field.element_type {
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::EQREF.shared().unwrap().into()),
_ => false,
}; if !is_valid_type {
bail!( self.offset, "invalid type: `struct.atomic.rmw.cmpxchg` only allows `i32`, `i64` and subtypes of `eqref`"
);
} let field_ty = field.element_type.unpack(); self.pop_operand(Some(field_ty))?; self.pop_operand(Some(field_ty))?; self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty)?;
Ok(())
} fn visit_array_new(&mutself, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(array_ty.element_type.unpack()))?; self.push_concrete_ref(false, type_index)
} fn visit_array_new_default(&mutself, type_index: u32) -> Self::Output { let ty = self.array_type_at(type_index)?; let val_ty = ty.element_type.unpack(); if !val_ty.is_defaultable() {
bail!( self.offset, "invalid `array.new_default`: {val_ty} field is not defaultable"
);
} self.pop_operand(Some(ValType::I32))?; self.push_concrete_ref(false, type_index)
} fn visit_array_new_fixed(&mutself, type_index: u32, n: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let elem_ty = array_ty.element_type.unpack(); for _ in0..n { self.pop_operand(Some(elem_ty))?;
} self.push_concrete_ref(false, type_index)
} fn visit_array_new_data(&mutself, type_index: u32, data_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let elem_ty = array_ty.element_type.unpack(); match elem_ty {
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {}
ValType::Ref(_) => bail!( self.offset, "type mismatch: array.new_data can only create arrays with numeric and vector elements"
),
} self.check_data_segment(data_index)?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.push_concrete_ref(false, type_index)
} fn visit_array_new_elem(&mutself, type_index: u32, elem_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let array_ref_ty = match array_ty.element_type.unpack() {
ValType::Ref(rt) => rt,
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => bail!( self.offset, "type mismatch: array.new_elem can only create arrays with reference elements"
),
}; let elem_ref_ty = self.element_type_at(elem_index)?; if !self
.resources
.is_subtype(elem_ref_ty.into(), array_ref_ty.into())
{
bail!( self.offset, "invalid array.new_elem instruction: element segment {elem_index} type mismatch: \
expected {array_ref_ty}, found {elem_ref_ty}"
)
} self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.push_concrete_ref(false, type_index)
} fn visit_array_get(&mutself, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let elem_ty = array_ty.element_type; if elem_ty.is_packed() {
bail!( self.offset, "cannot use array.get with packed storage types"
)
} self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty.unpack())
} fn visit_array_atomic_get(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.visit_array_get(type_index)?; // The `atomic` version has some additional type restrictions. let elem_ty = self.array_type_at(type_index)?.element_type; let is_valid_type = match elem_ty {
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::ANYREF.shared().unwrap().into()),
_ => false,
}; if !is_valid_type {
bail!( self.offset, "invalid type: `array.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`"
);
}
Ok(())
} fn visit_array_get_s(&mutself, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let elem_ty = array_ty.element_type; if !elem_ty.is_packed() {
bail!( self.offset, "cannot use array.get_s with non-packed storage types"
)
} self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty.unpack())
} fn visit_array_atomic_get_s(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.visit_array_get_s(type_index)?; // This instruction has the same type restrictions as the non-`atomic` version.
debug_assert!(matches!( self.array_type_at(type_index)?.element_type,
StorageType::I8 | StorageType::I16
));
Ok(())
} fn visit_array_get_u(&mutself, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let elem_ty = array_ty.element_type; if !elem_ty.is_packed() {
bail!( self.offset, "cannot use array.get_u with non-packed storage types"
)
} self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty.unpack())
} fn visit_array_atomic_get_u(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.visit_array_get_u(type_index)?; // This instruction has the same type restrictions as the non-`atomic` version.
debug_assert!(matches!( self.array_type_at(type_index)?.element_type,
StorageType::I8 | StorageType::I16
));
Ok(())
} fn visit_array_set(&mutself, type_index: u32) -> Self::Output { let array_ty = self.mutable_array_type_at(type_index)?; self.pop_operand(Some(array_ty.element_type.unpack()))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?;
Ok(())
} fn visit_array_atomic_set(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.visit_array_set(type_index)?; // The `atomic` version has some additional type restrictions. let elem_ty = self.array_type_at(type_index)?.element_type; let is_valid_type = match elem_ty {
StorageType::I8 | StorageType::I16 => true,
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::ANYREF.shared().unwrap().into()),
}; if !is_valid_type {
bail!( self.offset, "invalid type: `array.atomic.set` only allows `i8`, `i16`, `i32`, `i64` and subtypes of `anyref`"
);
}
Ok(())
} fn visit_array_len(&mutself) -> Self::Output { self.pop_maybe_shared_ref(AbstractHeapType::Array)?; self.push_operand(ValType::I32)
} fn visit_array_fill(&mutself, array_type_index: u32) -> Self::Output { let array_ty = self.mutable_array_type_at(array_type_index)?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(array_ty.element_type.unpack()))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, array_type_index)?;
Ok(())
} fn visit_array_copy(&mutself, type_index_dst: u32, type_index_src: u32) -> Self::Output { let array_ty_dst = self.mutable_array_type_at(type_index_dst)?; let array_ty_src = self.array_type_at(type_index_src)?; match (array_ty_dst.element_type, array_ty_src.element_type) {
(StorageType::I8, StorageType::I8) => {}
(StorageType::I8, ty) => bail!( self.offset, "array types do not match: expected i8, found {ty}"
),
(StorageType::I16, StorageType::I16) => {}
(StorageType::I16, ty) => bail!( self.offset, "array types do not match: expected i16, found {ty}"
),
(StorageType::Val(dst), StorageType::Val(src)) => { if !self.resources.is_subtype(src, dst) {
bail!( self.offset, "array types do not match: expected {dst}, found {src}"
)
}
}
(StorageType::Val(dst), src) => {
bail!( self.offset, "array types do not match: expected {dst}, found {src}"
)
}
} self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index_src)?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index_dst)?;
Ok(())
} fn visit_array_init_data(
&mutself,
array_type_index: u32,
array_data_index: u32,
) -> Self::Output { let array_ty = self.mutable_array_type_at(array_type_index)?; let val_ty = array_ty.element_type.unpack(); match val_ty {
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {}
ValType::Ref(_) => bail!( self.offset, "invalid array.init_data: array type is not numeric or vector"
),
} self.check_data_segment(array_data_index)?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, array_type_index)?;
Ok(())
} fn visit_array_init_elem(&mutself, type_index: u32, elem_index: u32) -> Self::Output { let array_ty = self.mutable_array_type_at(type_index)?; let array_ref_ty = match array_ty.element_type.unpack() {
ValType::Ref(rt) => rt,
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => bail!( self.offset, "type mismatch: array.init_elem can only create arrays with reference elements"
),
}; let elem_ref_ty = self.element_type_at(elem_index)?; if !self
.resources
.is_subtype(elem_ref_ty.into(), array_ref_ty.into())
{
bail!( self.offset, "invalid array.init_elem instruction: element segment {elem_index} type mismatch: \
expected {array_ref_ty}, found {elem_ref_ty}"
)
} self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?;
Ok(())
} fn visit_array_atomic_rmw_add(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.check_array_atomic_rmw("add", type_index)
} fn visit_array_atomic_rmw_sub(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.check_array_atomic_rmw("sub", type_index)
} fn visit_array_atomic_rmw_and(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.check_array_atomic_rmw("and", type_index)
} fn visit_array_atomic_rmw_or(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.check_array_atomic_rmw("or", type_index)
} fn visit_array_atomic_rmw_xor(&mutself, _ordering: Ordering, type_index: u32) -> Self::Output { self.check_array_atomic_rmw("xor", type_index)
} fn visit_array_atomic_rmw_xchg(
&mutself,
_ordering: Ordering,
type_index: u32,
) -> Self::Output { let field = self.mutable_array_type_at(type_index)?; let is_valid_type = match field.element_type {
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::ANYREF.shared().unwrap().into()),
_ => false,
}; if !is_valid_type {
bail!( self.offset, "invalid type: `array.atomic.rmw.xchg` only allows `i32`, `i64` and subtypes of `anyref`"
);
} let elem_ty = field.element_type.unpack(); self.pop_operand(Some(elem_ty))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty)?;
Ok(())
} fn visit_array_atomic_rmw_cmpxchg(
&mutself,
_ordering: Ordering,
type_index: u32,
) -> Self::Output { let field = self.mutable_array_type_at(type_index)?; let is_valid_type = match field.element_type {
StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true,
StorageType::Val(v) => self
.resources
.is_subtype(v, RefType::EQREF.shared().unwrap().into()),
_ => false,
}; if !is_valid_type {
bail!( self.offset, "invalid type: `array.atomic.rmw.cmpxchg` only allows `i32`, `i64` and subtypes of `eqref`"
);
} let elem_ty = field.element_type.unpack(); self.pop_operand(Some(elem_ty))?; self.pop_operand(Some(elem_ty))?; self.pop_operand(Some(ValType::I32))?; self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty)?;
Ok(())
} fn visit_any_convert_extern(&mutself) -> Self::Output { let any_ref = matchself.pop_maybe_shared_ref(AbstractHeapType::Extern)? {
MaybeType::Bottom | MaybeType::UnknownRef(_) => {
MaybeType::UnknownRef(Some(AbstractHeapType::Any))
}
MaybeType::Known(ty) => { let shared = self.resources.is_shared(ty); let heap_type = HeapType::Abstract {
shared,
ty: AbstractHeapType::Any,
}; let any_ref = RefType::new(ty.is_nullable(), heap_type).unwrap();
MaybeType::Known(any_ref)
}
}; self.push_operand(any_ref)
} fn visit_extern_convert_any(&mutself) -> Self::Output { let extern_ref = matchself.pop_maybe_shared_ref(AbstractHeapType::Any)? {
MaybeType::Bottom | MaybeType::UnknownRef(_) => {
MaybeType::UnknownRef(Some(AbstractHeapType::Extern))
}
MaybeType::Known(ty) => { let shared = self.resources.is_shared(ty); let heap_type = HeapType::Abstract {
shared,
ty: AbstractHeapType::Extern,
}; let extern_ref = RefType::new(ty.is_nullable(), heap_type).unwrap();
MaybeType::Known(extern_ref)
}
}; self.push_operand(extern_ref)
} fn visit_ref_test_non_null(&mutself, heap_type: HeapType) -> Self::Output { self.check_ref_test(false, heap_type)
} fn visit_ref_test_nullable(&mutself, heap_type: HeapType) -> Self::Output { self.check_ref_test(true, heap_type)
} fn visit_ref_cast_non_null(&mutself, heap_type: HeapType) -> Self::Output { self.check_ref_cast(false, heap_type)
} fn visit_ref_cast_nullable(&mutself, heap_type: HeapType) -> Self::Output { self.check_ref_cast(true, heap_type)
} fn visit_br_on_cast(
&mutself,
relative_depth: u32, mut from_ref_type: RefType, mut to_ref_type: RefType,
) -> Self::Output { self.resources
.check_ref_type(&mut from_ref_type, self.offset)?; self.resources
.check_ref_type(&mut to_ref_type, self.offset)?;
if !self
.resources
.is_subtype(to_ref_type.into(), from_ref_type.into())
{
bail!( self.offset, "type mismatch: expected {from_ref_type}, found {to_ref_type}"
);
}
let (block_ty, frame_kind) = self.jump(relative_depth)?; letmut label_types = self.label_types(block_ty, frame_kind)?;
match label_types.next_back() {
Some(label_ty) ifself.resources.is_subtype(to_ref_type.into(), label_ty) => { self.pop_operand(Some(from_ref_type.into()))?;
}
Some(label_ty) => bail!( self.offset, "type mismatch: casting to type {to_ref_type}, but it does not match \
label result type {label_ty}"
),
None => bail!( self.offset, "type mismatch: br_on_cast to label with empty types, must have a reference type"
),
};
if !self
.resources
.is_subtype(to_ref_type.into(), from_ref_type.into())
{
bail!( self.offset, "type mismatch: expected {from_ref_type}, found {to_ref_type}"
);
}
let (block_ty, frame_kind) = self.jump(relative_depth)?; letmut label_tys = self.label_types(block_ty, frame_kind)?;
let diff_ty = RefType::difference(from_ref_type, to_ref_type); match label_tys.next_back() {
Some(label_ty) ifself.resources.is_subtype(diff_ty.into(), label_ty) => { self.pop_operand(Some(from_ref_type.into()))?;
}
Some(label_ty) => bail!( self.offset, "type mismatch: expected label result type {label_ty}, found {diff_ty}"
),
None => bail!( self.offset, "type mismatch: expected a reference type, found nothing"
),
}
self.pop_push_label_types(label_tys)?; self.push_operand(to_ref_type)?;
Ok(())
} fn visit_ref_i31(&mutself) -> Self::Output { self.pop_operand(Some(ValType::I32))?; self.push_operand(ValType::Ref(RefType::I31))
} fn visit_ref_i31_shared(&mutself) -> Self::Output { self.pop_operand(Some(ValType::I32))?; self.push_operand(ValType::Ref(
RefType::I31.shared().expect("i31 is abstract"),
))
} fn visit_i31_get_s(&mutself) -> Self::Output { self.pop_maybe_shared_ref(AbstractHeapType::I31)?; self.push_operand(ValType::I32)
} fn visit_i31_get_u(&mutself) -> Self::Output { self.pop_maybe_shared_ref(AbstractHeapType::I31)?; self.push_operand(ValType::I32)
} fn visit_try(&mutself, mut ty: BlockType) -> Self::Output { self.check_block_type(&mut ty)?; for ty inself.params(ty)?.rev() { self.pop_operand(Some(ty))?;
} self.push_ctrl(FrameKind::LegacyTry, ty)?;
Ok(())
} fn visit_catch(&mutself, index: u32) -> Self::Output { let frame = self.pop_ctrl()?; if frame.kind != FrameKind::LegacyTry && frame.kind != FrameKind::LegacyCatch {
bail!(self.offset, "catch found outside of an `try` block");
} // Start a new frame and push `exnref` value. let height = self.operands.len(); let init_height = self.inits.len(); self.control.push(Frame {
kind: FrameKind::LegacyCatch,
block_type: frame.block_type,
height,
unreachable: false,
init_height,
}); // Push exception argument types. let ty = self.exception_tag_at(index)?; for ty in ty.params() { self.push_operand(*ty)?;
}
Ok(())
} fn visit_rethrow(&mutself, relative_depth: u32) -> Self::Output { // This is not a jump, but we need to check that the `rethrow` // targets an actual `catch` to get the exception. let (_, kind) = self.jump(relative_depth)?; if kind != FrameKind::LegacyCatch && kind != FrameKind::LegacyCatchAll {
bail!( self.offset, "invalid rethrow label: target was not a `catch` block"
);
} self.unreachable()?;
Ok(())
} fn visit_delegate(&mutself, relative_depth: u32) -> Self::Output { let frame = self.pop_ctrl()?; if frame.kind != FrameKind::LegacyTry {
bail!(self.offset, "delegate found outside of an `try` block");
} // This operation is not a jump, but we need to check the // depth for validity let _ = self.jump(relative_depth)?; for ty inself.results(frame.block_type)? { self.push_operand(ty)?;
}
Ok(())
} fn visit_catch_all(&mutself) -> Self::Output { let frame = self.pop_ctrl()?; if frame.kind == FrameKind::LegacyCatchAll {
bail!(self.offset, "only one catch_all allowed per `try` block");
} elseif frame.kind != FrameKind::LegacyTry && frame.kind != FrameKind::LegacyCatch {
bail!(self.offset, "catch_all found outside of a `try` block");
} let height = self.operands.len(); let init_height = self.inits.len(); self.control.push(Frame {
kind: FrameKind::LegacyCatchAll,
block_type: frame.block_type,
height,
unreachable: false,
init_height,
});
Ok(())
} fn visit_cont_new(&mutself, type_index: u32) -> Self::Output { let cont_ty = self.cont_type_at(type_index)?; let rt = RefType::concrete(true, cont_ty.0); self.pop_ref(Some(rt))?; self.push_concrete_ref(false, type_index)?;
Ok(())
} fn visit_cont_bind(&mutself, argument_index: u32, result_index: u32) -> Self::Output { // [ts1 ts1'] -> [ts2] let arg_cont = self.cont_type_at(argument_index)?; let arg_func = self.func_type_of_cont_type(arg_cont); // [ts1''] -> [ts2'] let res_cont = self.cont_type_at(result_index)?; let res_func = self.func_type_of_cont_type(res_cont);
// Verify that the argument's domain is at least as large as the // result's domain. if arg_func.params().len() < res_func.params().len() {
bail!(self.offset, "type mismatch in continuation arguments");
}
let argcnt = arg_func.params().len() - res_func.params().len();
// Check that [ts1'] -> [ts2] <: [ts1''] -> [ts2'] if !self.is_subtype_many(res_func.params(), &arg_func.params()[argcnt..])
|| arg_func.results().len() != res_func.results().len()
|| !self.is_subtype_many(arg_func.results(), res_func.results())
{
bail!(self.offset, "type mismatch in continuation types");
}
// Check that the continuation is available on the stack. self.pop_concrete_ref(true, argument_index)?;
// Check that the argument prefix is available on the stack. for &ty in arg_func.params().iter().take(argcnt).rev() { self.pop_operand(Some(ty))?;
}
// Construct the result type. self.push_concrete_ref(false, result_index)?;
Ok(())
} fn visit_suspend(&mutself, tag_index: u32) -> Self::Output { let ft = &self.tag_at(tag_index)?; for &ty in ft.params().iter().rev() { self.pop_operand(Some(ty))?;
} for &ty in ft.results() { self.push_operand(ty)?;
}
Ok(())
} fn visit_resume(&mutself, type_index: u32, table: ResumeTable) -> Self::Output { // [ts1] -> [ts2] let ft = self.check_resume_table(table, type_index)?; self.pop_concrete_ref(true, type_index)?; // Check that ts1 are available on the stack. for &ty in ft.params().iter().rev() { self.pop_operand(Some(ty))?;
}
// Make ts2 available on the stack. for &ty in ft.results() { self.push_operand(ty)?;
}
Ok(())
} fn visit_resume_throw(
&mutself,
type_index: u32,
tag_index: u32,
table: ResumeTable,
) -> Self::Output { // [ts1] -> [ts2] let ft = self.check_resume_table(table, type_index)?; // [ts1'] -> [] let tag_ty = self.exception_tag_at(tag_index)?; if tag_ty.results().len() != 0 {
bail!(self.offset, "type mismatch: non-empty tag result type")
} self.pop_concrete_ref(true, type_index)?; // Check that ts1' are available on the stack. for &ty in tag_ty.params().iter().rev() { self.pop_operand(Some(ty))?;
}
// Make ts2 available on the stack. for &ty in ft.results() { self.push_operand(ty)?;
}
Ok(())
} fn visit_switch(&mutself, type_index: u32, tag_index: u32) -> Self::Output { // [t1* (ref null $ct2)] -> [te1*] let cont_ty = self.cont_type_at(type_index)?; let func_ty = self.func_type_of_cont_type(cont_ty); // [] -> [t*] let tag_ty = self.tag_at(tag_index)?; if tag_ty.params().len() != 0 {
bail!(self.offset, "type mismatch: non-empty tag parameter type")
} // Extract the other continuation reference match func_ty.params().last() {
Some(ValType::Ref(rt)) if rt.is_concrete_type_ref() => { let other_cont_id = rt
.type_index()
.unwrap()
.unpack()
.as_core_type_id()
.expect("expected canonicalized index"); let sub_ty = self.resources.sub_type_at_id(other_cont_id); let other_cont_ty = iflet CompositeInnerType::Cont(cont) = &sub_ty.composite_type.inner {
cont
} else {
bail!(self.offset, "non-continuation type");
}; let other_func_ty = self.func_type_of_cont_type(&other_cont_ty); if func_ty.results().len() != tag_ty.results().len()
|| !self.is_subtype_many(func_ty.results(), tag_ty.results())
|| other_func_ty.results().len() != tag_ty.results().len()
|| !self.is_subtype_many(tag_ty.results(), other_func_ty.results())
{
bail!(self.offset, "type mismatch in continuation types")
}
// Pop the continuation reference. self.pop_concrete_ref(true, type_index)?;
// Check that the arguments t1* are available on the // stack. for &ty in func_ty.params().iter().rev().skip(1) { self.pop_operand(Some(ty))?;
}
// Make the results t2* available on the stack. for &ty in other_func_ty.params() { self.push_operand(ty)?;
}
}
Some(ty) => bail!( self.offset, "type mismatch: expected a continuation reference, found {}",
ty_to_str(*ty)
),
None => bail!( self.offset, "type mismatch: instruction requires a continuation reference"
),
}
Ok(())
} fn visit_i64_add128(&mutself) -> Result<()> { self.check_binop128()
} fn visit_i64_sub128(&mutself) -> Result<()> { self.check_binop128()
} fn visit_i64_mul_wide_s(&mutself) -> Result<()> { self.check_i64_mul_wide()
} fn visit_i64_mul_wide_u(&mutself) -> Result<()> { self.check_i64_mul_wide()
}
}
impl Locals { /// Defines another group of `count` local variables of type `ty`. /// /// Returns `true` if the definition was successful. Local variable /// definition is unsuccessful in case the amount of total variables /// after definition exceeds the allowed maximum number. fn define(&mutself, count: u32, ty: ValType) -> bool { matchself.num_locals.checked_add(count) {
Some(n) => self.num_locals = n,
None => returnfalse,
} ifself.num_locals > (MAX_WASM_FUNCTION_LOCALS as u32) { returnfalse;
} for _ in0..count { ifself.first.len() >= MAX_LOCALS_TO_TRACK { break;
} self.first.push(ty);
} self.all.push((self.num_locals - 1, ty)); true
}
/// Returns the number of defined local variables. pub(super) fn len_locals(&self) -> u32 { self.num_locals
}
/// Returns the type of the local variable at the given index if any. #[inline] pub(super) fn get(&self, idx: u32) -> Option<ValType> { matchself.first.get(idx as usize) {
Some(ty) => Some(*ty),
None => self.get_bsearch(idx),
}
}
fn get_bsearch(&self, idx: u32) -> Option<ValType> { matchself.all.binary_search_by_key(&idx, |(idx, _)| *idx) { // If this index would be inserted at the end of the list, then the // index is out of bounds and we return an error.
Err(i) if i == self.all.len() => None,
// If `Ok` is returned we found the index exactly, or if `Err` is // returned the position is the one which is the least index // greater that `idx`, which is still the type of `idx` according // to our "compressed" representation. In both cases we access the // list at index `i`.
Ok(i) | Err(i) => Some(self.all[i].1),
}
}
}
impl<R> ModuleArity for WasmProposalValidator<'_, '_, R> where
R: WasmModuleResources,
{ fn tag_type_arity(&self, at: u32) -> Option<(u32, u32)> { self.0
.resources
.tag_at(at)
.map(|x| (x.params().len() as u32, x.results().len() as u32))
}
¤ 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.0.296Bemerkung:
(vorverarbeitet am 2026-06-26)
¤
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.