Skip to content

Commit

Permalink
Add Module::validate API from Wasmtime (#840)
Browse files Browse the repository at this point in the history
* remove unneeded wasmparser::validate checks in some test cases

* no longer panic in parser when encountering component model definitions

Instead return a proper wasmi error indicating usage of unsupported Wasm features.

* add Module::validate API

* remove unnecessary temporary buffer

We do not need this buffer until we actually plan to perform validation in parllel.
  • Loading branch information
Robbepop committed Dec 8, 2023
1 parent 336bb11 commit bed9ae4
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 10 deletions.
2 changes: 0 additions & 2 deletions crates/wasmi/src/engine/translator/tests/regression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ fn fuzz_regression_4() {
fn fuzz_regression_5() {
let wat = include_str!("fuzz_5.wat");
let wasm = wat2wasm(wat);
_ = wasmparser::validate(&wasm[..]).unwrap();
TranslationTest::new(wasm)
.expect_func_instrs([
Instruction::call_internal(
Expand All @@ -124,7 +123,6 @@ fn fuzz_regression_5() {
fn fuzz_regression_6() {
let wat = include_str!("fuzz_6.wat");
let wasm = wat2wasm(wat);
_ = wasmparser::validate(&wasm[..]).unwrap();
TranslationTest::new(wasm)
.expect_func_instrs([
Instruction::branch_i32_eq_imm(Register::from_i16(0), 0, BranchOffset16::from(4)),
Expand Down
24 changes: 21 additions & 3 deletions crates/wasmi/src/module/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,36 @@ pub enum ModuleError {
Parser(ParserError),
/// Encountered when there is a Wasm to `wasmi` translation error.
Translation(TranslationError),
/// Encountered unsupported Wasm feature usage.
Unsupported(UnsupportedFeature),
}

/// An unsupported Wasm feature.
#[derive(Debug)]
pub enum UnsupportedFeature {
/// The Wasm component model.
ComponentModel,
}

impl Display for ModuleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModuleError::Read(error) => Display::fmt(error, f),
ModuleError::Parser(error) => Display::fmt(error, f),
ModuleError::Translation(error) => Display::fmt(error, f),
Self::Read(error) => Display::fmt(error, f),
Self::Parser(error) => Display::fmt(error, f),
Self::Translation(error) => Display::fmt(error, f),
Self::Unsupported(feature) => {
write!(f, "encountered unsupported Wasm feature: {feature:?}")
}
}
}
}

impl From<UnsupportedFeature> for ModuleError {
fn from(feature: UnsupportedFeature) -> Self {
Self::Unsupported(feature)
}
}

impl From<ReadError> for ModuleError {
fn from(error: ReadError) -> Self {
Self::Read(error)
Expand Down
47 changes: 47 additions & 0 deletions crates/wasmi/src/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub(crate) use self::{
};
use crate::{
engine::{CompiledFunc, DedupFuncType},
module::error::UnsupportedFeature,
Engine,
Error,
ExternType,
Expand All @@ -48,6 +49,7 @@ use crate::{
};
use alloc::{boxed::Box, collections::BTreeMap, sync::Arc};
use core::{iter, slice::Iter as SliceIter};
use wasmparser::{FuncValidatorAllocations, Parser, ValidPayload, Validator};

/// A parsed and validated WebAssembly module.
#[derive(Debug)]
Expand Down Expand Up @@ -187,6 +189,51 @@ impl Module {
&self.engine
}

/// Validates `wasm` as a WebAssembly binary given the configuration (via [`Config`]) in `engine`.
///
/// This function performs Wasm validation of the binary input WebAssembly module and
/// returns either `Ok`` or `Err`` depending on the results of the validation.
/// The [`Config`] of the `engine` is used for Wasm validation which indicates which WebAssembly
/// features are valid and invalid for the validation.
///
/// # Note
///
/// - The input `wasm` must be in binary form, the text format is not accepted by this function.
/// - This will only validate the `wasm` but not try to translate it. Therefore `Module::new`
/// might still fail if translation of the Wasm binary input fails to translate via the `wasmi`
/// [`Engine`].
/// - Validation automatically happens as part of [`Module::new`].
///
/// # Errors
///
/// If Wasm validation for `wasm` fails for the given [`Config`] provided via `engine`.
///
/// [`Config`]: crate::Config
pub fn validate(&self, engine: &Engine, wasm: &[u8]) -> Result<(), Error> {
let mut validator = Validator::new_with_features(engine.config().wasm_features());
for payload in Parser::new(0).parse_all(wasm) {
let payload = payload.map_err(ModuleError::from)?;
if let ValidPayload::Func(func_to_validate, func_body) =
validator.payload(&payload).map_err(ModuleError::from)?
{
func_to_validate
.into_validator(FuncValidatorAllocations::default())
.validate(&func_body)
.map_err(ModuleError::from)?;
}
if let wasmparser::Payload::Version {
encoding: wasmparser::Encoding::Component,
..
} = &payload
{
return Err(Error::from(ModuleError::from(
UnsupportedFeature::ComponentModel,
)));
}
}
Ok(())
}

/// Creates a new [`Module`] from the [`ModuleBuilder`].
fn from_builder(builder: ModuleBuilder) -> Self {
Self {
Expand Down
8 changes: 3 additions & 5 deletions crates/wasmi/src/module/parser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{
compile::{translate, translate_unchecked},
error::UnsupportedFeature,
export::ExternIdx,
global::Global,
import::{FuncTypeIdx, Import},
Expand Down Expand Up @@ -572,12 +573,9 @@ impl<'engine> ModuleParser<'engine> {
/// Process the entries for the Wasm component model proposal.
fn process_unsupported_component_model(
&mut self,
range: Range<usize>,
_range: Range<usize>,
) -> Result<(), ModuleError> {
panic!(
"wasmi does not support the `component-model` Wasm proposal: bytes[{}..{}]",
range.start, range.end
)
Err(ModuleError::from(UnsupportedFeature::ComponentModel))
}

/// Process an unknown Wasm module section.
Expand Down

0 comments on commit bed9ae4

Please sign in to comment.