diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 21c2d3dd7f..64605f6409 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -100,7 +100,7 @@ impl<'a> WasmBackend<'a> { }); // TODO: move this to module parsing let next_constant_addr = module.global.parse_u32_at_index(data_end_idx).unwrap_or_else(|e| { - internal_error!("Failed to parse __data_end from object file: {}", e); + internal_error!("Failed to parse __data_end from object file: {:?}", e); }); module.export.exports = app_exports; diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 52d60096c6..9d73f8a182 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -17,6 +17,7 @@ use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use roc_target::TargetInfo; +use wasm_module::parse::ParseError; use crate::backend::{ProcLookupData, ProcSource, WasmBackend}; use crate::wasm_module::{ @@ -59,7 +60,8 @@ pub fn build_module<'a>( procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { let (mut wasm_module, called_preload_fns, _) = - build_module_unserialized(env, interns, preload_bytes, procedures)?; + build_module_unserialized(env, interns, preload_bytes, procedures) + .map_err(|e| format!("{:?}", e))?; wasm_module.remove_dead_preloads(env.arena, called_preload_fns); @@ -77,7 +79,7 @@ pub fn build_module_unserialized<'a>( interns: &'a mut Interns, preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result<(WasmModule<'a>, Vec<'a, u32>, u32), String> { +) -> Result<(WasmModule<'a>, Vec<'a, u32>, u32), ParseError> { let mut layout_ids = LayoutIds::default(); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut proc_lookup = Vec::with_capacity_in(procedures.len() * 2, env.arena); diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 198d53c8ba..9e8dd5e07f 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -121,10 +121,9 @@ pub fn parse_preloads_call_graph<'a>( u32::skip_bytes(code_section_body, &mut cursor).unwrap(); // table_idx } else { OpCode::skip_bytes(code_section_body, &mut cursor).unwrap_or_else(|e| { - let display_slice = &code_section_body[cursor - 4..cursor + 4]; panic!( - "Error parsing host object file, at offset {} (0x{:x}) within Code section: {}\nbytes around the error: {:x?}\nstart of code section: {:x?}", - cursor, cursor, e, display_slice, &code_section_body[0..16] + "Error parsing host object file, at offset 0x{:x} within Code section: {}", + cursor, e.message ) }); } diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index d6715b916e..ee40fc0207 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -2,9 +2,9 @@ pub mod code_builder; mod dead_code; pub mod linking; pub mod opcodes; +pub mod parse; pub mod sections; pub mod serialize; -pub mod parse; use bumpalo::{collections::Vec, Bump}; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; @@ -12,6 +12,7 @@ pub use linking::SymInfo; pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}; use self::linking::{LinkingSection, RelocationSection}; +use self::parse::ParseError; use self::sections::{ CodeSection, DataSection, ElementSection, ExportSection, FunctionSection, GlobalSection, ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TableSection, @@ -121,11 +122,15 @@ impl<'a> WasmModule<'a> { + self.names.size() } - pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Result { + pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Result { let is_valid_magic_number = &bytes[0..4] == "\0asm".as_bytes(); let is_valid_version = bytes[4..8] == Self::WASM_VERSION.to_le_bytes(); if !is_valid_magic_number || !is_valid_version { - return Err("This file is not a WebAssembly binary. The file header is not valid.".into()); + return Err(ParseError { + offset: 0, + message: "This file is not a WebAssembly binary. The file header is not valid." + .into(), + }); } let mut cursor: usize = 8; diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index 5836f6b0e3..e5674dc2b2 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -1,4 +1,4 @@ -use super::parse::{Parse, SkipBytes}; +use super::parse::{Parse, ParseError, SkipBytes}; #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -259,13 +259,17 @@ fn immediates_for(op: OpCode) -> Result { } impl SkipBytes for OpCode { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { use OpImmediates::*; let opcode_byte: u8 = bytes[*cursor]; let opcode: OpCode = unsafe { std::mem::transmute(opcode_byte) }; - let immediates = immediates_for(opcode)?; // will return Err if transmute was invalid + // will return Err if transmute was invalid + let immediates = immediates_for(opcode).map_err(|message| ParseError { + message, + offset: *cursor, + })?; match immediates { NoImmediate => { diff --git a/compiler/gen_wasm/src/wasm_module/parse.rs b/compiler/gen_wasm/src/wasm_module/parse.rs index b90c4eb582..99fb2c28ec 100644 --- a/compiler/gen_wasm/src/wasm_module/parse.rs +++ b/compiler/gen_wasm/src/wasm_module/parse.rs @@ -4,18 +4,24 @@ use bumpalo::Bump; /// Parse serialized bytes into a data structure /// Specific parsers may need contextual data from other parts of the .wasm file pub trait Parse: Sized { - fn parse(ctx: ParseContext, bytes: &[u8], cursor: &mut usize) -> Result; + fn parse(ctx: ParseContext, bytes: &[u8], cursor: &mut usize) -> Result; +} + +#[derive(Debug)] +pub struct ParseError { + pub offset: usize, + pub message: String, } /// Skip over serialized bytes for a type /// This may, or may not, require looking at the byte values pub trait SkipBytes: Sized { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String>; + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError>; } /// Decode an unsigned 32-bit integer from the provided buffer in LEB-128 format /// Return the integer itself and the offset after it ends -fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), String> { +fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), ()> { let mut value = 0; let mut shift = 0; for (i, byte) in bytes.iter().take(MAX_SIZE_ENCODED_U32).enumerate() { @@ -25,23 +31,30 @@ fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), String> { } shift += 7; } - Err(format!( - "Failed to decode u32 as LEB-128 from bytes: {:2x?}", - std::vec::Vec::from_iter(bytes.iter().take(MAX_SIZE_ENCODED_U32)) - )) + Err(()) } impl Parse<()> for u32 { - fn parse(_ctx: (), bytes: &[u8], cursor: &mut usize) -> Result { - let (value, len) = decode_u32(&bytes[*cursor..])?; - *cursor += len; - Ok(value) + fn parse(_ctx: (), bytes: &[u8], cursor: &mut usize) -> Result { + match decode_u32(&bytes[*cursor..]) { + Ok((value, len)) => { + *cursor += len; + Ok(value) + } + Err(()) => Err(ParseError { + offset: *cursor, + message: format!( + "Failed to decode u32 as LEB-128 from bytes: {:2x?}", + &bytes[*cursor..][..MAX_SIZE_ENCODED_U32] + ), + }), + } } } // Parse string bytes without utf8 validation impl<'a> Parse<&'a Bump> for &'a [u8] { - fn parse(arena: &'a Bump, bytes: &[u8], cursor: &mut usize) -> Result { + fn parse(arena: &'a Bump, bytes: &[u8], cursor: &mut usize) -> Result { let len = u32::parse((), bytes, cursor)?; let end = *cursor + len as usize; let bytes: &[u8] = &bytes[*cursor..end]; @@ -52,7 +65,7 @@ impl<'a> Parse<&'a Bump> for &'a [u8] { } impl SkipBytes for u32 { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { const MAX_LEN: usize = 5; for (i, byte) in bytes.iter().enumerate().skip(*cursor).take(MAX_LEN) { if byte & 0x80 == 0 { @@ -60,12 +73,15 @@ impl SkipBytes for u32 { return Ok(()); } } - Err("Invalid LEB encoding".into()) + Err(ParseError { + offset: *cursor, + message: "Invalid LEB encoding".into(), + }) } } impl SkipBytes for u64 { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { const MAX_LEN: usize = 10; for (i, byte) in bytes.iter().enumerate().skip(*cursor).take(MAX_LEN) { if byte & 0x80 == 0 { @@ -73,12 +89,15 @@ impl SkipBytes for u64 { return Ok(()); } } - Err("Invalid LEB encoding".into()) + Err(ParseError { + offset: *cursor, + message: "Invalid LEB encoding".into(), + }) } } impl SkipBytes for u8 { - fn skip_bytes(_bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(_bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { *cursor += 1; Ok(()) } @@ -86,7 +105,7 @@ impl SkipBytes for u8 { /// Note: This is just for skipping over Wasm bytes. We don't actually care about String vs str! impl SkipBytes for String { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { let len = u32::parse((), bytes, cursor)?; if false { diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 7eafa1d885..401f28f267 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -11,7 +11,7 @@ use super::dead_code::{ }; use super::linking::RelocationEntry; use super::opcodes::OpCode; -use super::parse::{Parse, SkipBytes}; +use super::parse::{Parse, ParseError, SkipBytes}; use super::serialize::{SerialBuffer, Serialize, MAX_SIZE_ENCODED_U32}; use super::{CodeBuilder, ValueType}; @@ -351,7 +351,7 @@ impl<'a> ImportSection<'a> { self.count += 1; } - pub fn parse(&mut self, arena: &'a Bump) -> Result, String> { + pub fn parse(&mut self, arena: &'a Bump) -> Result, ParseError> { let mut fn_signatures = bumpalo::vec![in arena]; let mut cursor = 0; while cursor < self.bytes.len() { @@ -459,7 +459,7 @@ impl Serialize for TableType { } impl SkipBytes for TableType { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { u8::skip_bytes(bytes, cursor)?; Limits::skip_bytes(bytes, cursor)?; Ok(()) @@ -563,7 +563,7 @@ impl Serialize for Limits { } impl SkipBytes for Limits { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { let variant_id = bytes[*cursor]; u8::skip_bytes(bytes, cursor)?; // advance past the variant byte u32::skip_bytes(bytes, cursor)?; // skip "min" @@ -638,7 +638,7 @@ impl Serialize for GlobalType { } impl SkipBytes for GlobalType { - fn skip_bytes(_bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(_bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { *cursor += 2; Ok(()) } @@ -655,15 +655,18 @@ pub enum ConstExpr { } impl ConstExpr { - fn parse_u32(bytes: &[u8], cursor: &mut usize) -> Result { - let err = Err("Invalid ConstExpr. Expected i32.".into()); + fn parse_u32(bytes: &[u8], cursor: &mut usize) -> Result { + let err = Err(ParseError { + offset: *cursor, + message: "Invalid ConstExpr. Expected i32.".into(), + }); if bytes[*cursor] != OpCode::I32CONST as u8 { return err; } *cursor += 1; - let value = u32::parse((), bytes, cursor).unwrap(); + let value = u32::parse((), bytes, cursor)?; if bytes[*cursor] != OpCode::END as u8 { return err; @@ -706,7 +709,7 @@ impl Serialize for ConstExpr { } impl SkipBytes for ConstExpr { - fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), String> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) -> Result<(), ParseError> { while bytes[*cursor] != OpCode::END as u8 { OpCode::skip_bytes(bytes, cursor)?; } @@ -737,7 +740,7 @@ pub struct GlobalSection<'a> { } impl<'a> GlobalSection<'a> { - pub fn parse_u32_at_index(&self, index: u32) -> Result { + pub fn parse_u32_at_index(&self, index: u32) -> Result { let mut cursor = 0; for _ in 0..index { GlobalType::skip_bytes(&self.bytes, &mut cursor)?;