summaryrefslogtreecommitdiff
path: root/crates/parser/src/lib.rs
blob: 7beb5f8aad9828b61a0acc11cc5e9e0183249a60 (plain)
#![no_std]
#![doc(test(
    no_crate_inject,
    attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables))
))]
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)]
#![forbid(unsafe_code)]
#![cfg_attr(not(feature = "std"), feature(error_in_core))]
//! See [`tinywasm`](https://docs.rs/tinywasm) for documentation.

mod std;
extern crate alloc;

// log for logging (optional).
#[cfg(feature = "logging")]
#[allow(clippy::single_component_path_imports)]
use log;

// noop fallback if logging is disabled.
#[cfg(not(feature = "logging"))]
mod log {
    macro_rules! debug    ( ($($tt:tt)*) => {{}} );
    macro_rules! error    ( ($($tt:tt)*) => {{}} );
    pub(crate) use debug;
    pub(crate) use error;
}

mod conversion;
mod error;
mod module;
mod visit;
use alloc::{string::ToString, vec::Vec};
pub use error::*;
use module::ModuleReader;
use tinywasm_types::{TypedWasmFunction, WasmFunction};
use wasmparser::{Validator, WasmFeatures};

pub use tinywasm_types::TinyWasmModule;

/// A WebAssembly parser
#[derive(Default, Debug)]
pub struct Parser {}

impl Parser {
    /// Create a new parser instance
    pub fn new() -> Self {
        Self {}
    }

    fn create_validator(&self) -> Validator {
        let features = WasmFeatures {
            bulk_memory: true,
            floats: true,
            multi_value: true,
            mutable_global: true,
            reference_types: true,
            sign_extension: true,
            saturating_float_to_int: true,

            function_references: false,
            component_model: false,
            component_model_nested_names: false,
            component_model_values: false,
            exceptions: false,
            extended_const: false,
            gc: false,
            memory64: false,
            memory_control: false,
            relaxed_simd: false,
            simd: false,
            tail_call: false,
            threads: false,
            multi_memory: false, // should be working mostly
        };
        Validator::new_with_features(features)
    }

    /// Parse a [`TinyWasmModule`] from bytes
    pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result<TinyWasmModule> {
        let wasm = wasm.as_ref();
        let mut validator = self.create_validator();
        let mut reader = ModuleReader::new();

        for payload in wasmparser::Parser::new(0).parse_all(wasm) {
            reader.process_payload(payload?, &mut validator)?;
        }

        if !reader.end_reached {
            return Err(ParseError::EndNotReached);
        }

        reader.try_into()
    }

    #[cfg(feature = "std")]
    /// Parse a [`TinyWasmModule`] from a file. Requires `std` feature.
    pub fn parse_module_file(&self, path: impl AsRef<crate::std::path::Path> + Clone) -> Result<TinyWasmModule> {
        use alloc::format;
        let f = crate::std::fs::File::open(path.clone())
            .map_err(|e| ParseError::Other(format!("Error opening file {:?}: {}", path.as_ref(), e)))?;

        let mut reader = crate::std::io::BufReader::new(f);
        self.parse_module_stream(&mut reader)
    }

    #[cfg(feature = "std")]
    /// Parse a [`TinyWasmModule`] from a stream. Requires `std` feature.
    pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result<TinyWasmModule> {
        use alloc::format;

        let mut validator = self.create_validator();
        let mut reader = ModuleReader::new();
        let mut buffer = Vec::new();
        let mut parser = wasmparser::Parser::new(0);
        let mut eof = false;

        loop {
            match parser.parse(&buffer, eof)? {
                wasmparser::Chunk::NeedMoreData(hint) => {
                    let len = buffer.len();
                    buffer.extend((0..hint).map(|_| 0u8));
                    let read_bytes = stream
                        .read(&mut buffer[len..])
                        .map_err(|e| ParseError::Other(format!("Error reading from stream: {}", e)))?;
                    buffer.truncate(len + read_bytes);
                    eof = read_bytes == 0;
                }
                wasmparser::Chunk::Parsed { consumed, payload } => {
                    reader.process_payload(payload, &mut validator)?;
                    buffer.drain(..consumed);
                    if eof || reader.end_reached {
                        return reader.try_into();
                    }
                }
            };
        }
    }
}

impl TryFrom<ModuleReader> for TinyWasmModule {
    type Error = ParseError;

    fn try_from(reader: ModuleReader) -> Result<Self> {
        if !reader.end_reached {
            return Err(ParseError::EndNotReached);
        }

        let code_type_addrs = reader.code_type_addrs;
        let local_function_count = reader.code.len();

        if code_type_addrs.len() != local_function_count {
            return Err(ParseError::Other("Code and code type address count mismatch".to_string()));
        }

        let funcs = reader
            .code
            .into_iter()
            .zip(code_type_addrs)
            .map(|((instructions, locals), ty_idx)| TypedWasmFunction {
                type_addr: ty_idx,
                wasm_function: WasmFunction {
                    instructions,
                    locals,
                    ty: reader.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(),
                },
            })
            .collect::<Vec<_>>();

        let globals = reader.globals;
        let table_types = reader.table_types;

        Ok(TinyWasmModule {
            funcs: funcs.into_boxed_slice(),
            func_types: reader.func_types.into_boxed_slice(),
            globals: globals.into_boxed_slice(),
            table_types: table_types.into_boxed_slice(),
            imports: reader.imports.into_boxed_slice(),
            version: reader.version,
            start_func: reader.start_func,
            data: reader.data.into_boxed_slice(),
            exports: reader.exports.into_boxed_slice(),
            elements: reader.elements.into_boxed_slice(),
            memory_types: reader.memory_types.into_boxed_slice(),
        })
    }
}