Skip to main content

baedeker_core/
error.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Error types for binary decoding.
5//!
6//! All errors carry byte offsets into the original binary and structured context,
7//! enabling precise diagnostic messages.
8
9use core::fmt;
10
11/// The byte offset into the WASM binary where an error occurred.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct ByteOffset(pub usize);
15
16/// Contextual information about what was being decoded when the error occurred.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum DecodeContext {
19    /// Decoding the WASM magic number.
20    Magic,
21    /// Decoding the WASM version number.
22    Version,
23    /// Decoding a section header.
24    SectionHeader,
25    /// Decoding section contents.
26    SectionBody { id: u8 },
27    /// Decoding a LEB128 value.
28    Leb128,
29    /// Decoding a type section entry.
30    TypeSection,
31    /// Decoding an import section entry.
32    ImportSection,
33    /// Decoding a function section entry.
34    FunctionSection,
35    /// Decoding a table section entry.
36    TableSection,
37    /// Decoding a global section entry.
38    GlobalSection,
39    /// Decoding a memory section entry.
40    MemorySection,
41    /// Decoding an export section entry.
42    ExportSection,
43    /// Decoding a start section entry.
44    StartSection,
45    /// Decoding an element section entry.
46    ElementSection,
47    /// Decoding a data section entry.
48    DataSection,
49    /// Decoding a data count section entry.
50    DataCountSection,
51    /// Decoding a code section entry.
52    CodeSection,
53}
54
55impl fmt::Display for DecodeContext {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            DecodeContext::Magic => write!(f, "WASM magic number"),
59            DecodeContext::Version => write!(f, "WASM version"),
60            DecodeContext::SectionHeader => write!(f, "section header"),
61            DecodeContext::SectionBody { id } => write!(f, "section body (id={id})"),
62            DecodeContext::Leb128 => write!(f, "LEB128 value"),
63            DecodeContext::TypeSection => write!(f, "type section"),
64            DecodeContext::ImportSection => write!(f, "import section"),
65            DecodeContext::FunctionSection => write!(f, "function section"),
66            DecodeContext::TableSection => write!(f, "table section"),
67            DecodeContext::GlobalSection => write!(f, "global section"),
68            DecodeContext::MemorySection => write!(f, "memory section"),
69            DecodeContext::ExportSection => write!(f, "export section"),
70            DecodeContext::StartSection => write!(f, "start section"),
71            DecodeContext::ElementSection => write!(f, "element section"),
72            DecodeContext::DataSection => write!(f, "data section"),
73            DecodeContext::DataCountSection => write!(f, "data count section"),
74            DecodeContext::CodeSection => write!(f, "code section"),
75        }
76    }
77}
78
79/// Errors that can occur during binary decoding.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct DecodeError {
82    /// Byte offset into the binary where the error was detected.
83    pub offset: ByteOffset,
84    /// What was being decoded.
85    pub context: DecodeContext,
86    /// The specific error kind.
87    pub kind: DecodeErrorKind,
88}
89
90/// Specific categories of decode errors.
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum DecodeErrorKind {
93    /// Unexpected end of input.
94    UnexpectedEof,
95    /// Invalid magic number (expected `\0asm`).
96    InvalidMagic,
97    /// Unsupported WASM version.
98    UnsupportedVersion { found: u32 },
99    /// LEB128 encoding exceeds the maximum number of bytes for the target type.
100    Leb128TooLong,
101    /// LEB128 encoding has unused bits set in the final byte (overlong/overflow).
102    Leb128Overflow,
103    /// Unknown section ID.
104    UnknownSectionId { id: u8 },
105    /// Section extends beyond the end of the binary.
106    SectionOverflow,
107    /// Sections are out of order (non-custom sections must be ordered by ID).
108    SectionOutOfOrder { prev: u8, current: u8 },
109    /// Duplicate non-custom section.
110    DuplicateSection { id: u8 },
111    /// Unknown value type encoding byte.
112    UnknownValType { byte: u8 },
113    /// Unknown reference type encoding byte.
114    UnknownRefType { byte: u8 },
115    /// Unknown import descriptor tag.
116    UnknownImportDesc { byte: u8 },
117    /// Unknown export descriptor tag.
118    UnknownExportDesc { byte: u8 },
119    /// Invalid global mutability encoding.
120    InvalidMutability { byte: u8 },
121    /// Invalid UTF-8 in a name string.
122    InvalidUtf8,
123    /// Function and code section counts disagree.
124    FunctionCodeLengthMismatch { functions: u32, codes: u32 },
125    /// Total local count exceeds the spec maximum (2^32 - 1).
126    TooManyLocals,
127    /// Unknown instruction opcode.
128    UnknownOpcode { byte: u8 },
129    /// Unknown SIMD-prefixed instruction opcode.
130    UnknownSimdOpcode { opcode: u32 },
131    /// Unexpected byte value.
132    UnexpectedByte { expected: u8, found: u8 },
133    /// Section body was not fully consumed.
134    SectionSizeMismatch { expected: u32, consumed: u32 },
135}
136
137impl fmt::Display for DecodeError {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        write!(
140            f,
141            "decode error at byte {}: {}: {}",
142            self.offset.0, self.context, self.kind
143        )
144    }
145}
146
147impl fmt::Display for DecodeErrorKind {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        match self {
150            DecodeErrorKind::UnexpectedEof => write!(f, "unexpected end of input"),
151            DecodeErrorKind::InvalidMagic => write!(f, "invalid magic number (expected \\0asm)"),
152            DecodeErrorKind::UnsupportedVersion { found } => {
153                write!(f, "unsupported WASM version {found} (expected 1)")
154            }
155            DecodeErrorKind::Leb128TooLong => write!(f, "LEB128 encoding too long"),
156            DecodeErrorKind::TooManyLocals => write!(f, "too many locals"),
157            DecodeErrorKind::Leb128Overflow => write!(f, "LEB128 overflow (unused bits set)"),
158            DecodeErrorKind::UnknownSectionId { id } => {
159                write!(f, "unknown section ID {id:#04x}")
160            }
161            DecodeErrorKind::SectionOverflow => {
162                write!(f, "section extends beyond end of binary")
163            }
164            DecodeErrorKind::SectionOutOfOrder { prev, current } => {
165                write!(
166                    f,
167                    "section {current} appears after section {prev} (out of order)"
168                )
169            }
170            DecodeErrorKind::DuplicateSection { id } => {
171                write!(f, "duplicate section (id={id})")
172            }
173            DecodeErrorKind::UnknownValType { byte } => {
174                write!(f, "unknown value type {byte:#04x}")
175            }
176            DecodeErrorKind::UnknownRefType { byte } => {
177                write!(f, "unknown reference type {byte:#04x}")
178            }
179            DecodeErrorKind::UnknownImportDesc { byte } => {
180                write!(f, "unknown import descriptor {byte:#04x}")
181            }
182            DecodeErrorKind::UnknownExportDesc { byte } => {
183                write!(f, "unknown export descriptor {byte:#04x}")
184            }
185            DecodeErrorKind::InvalidMutability { byte } => {
186                write!(f, "invalid mutability {byte:#04x}")
187            }
188            DecodeErrorKind::InvalidUtf8 => write!(f, "invalid UTF-8 string"),
189            DecodeErrorKind::FunctionCodeLengthMismatch { functions, codes } => {
190                write!(
191                    f,
192                    "function/code section length mismatch: {functions} declarations, {codes} bodies"
193                )
194            }
195            DecodeErrorKind::UnknownOpcode { byte } => {
196                write!(f, "unknown opcode {byte:#04x}")
197            }
198            DecodeErrorKind::UnknownSimdOpcode { opcode } => {
199                write!(f, "unknown SIMD opcode {opcode:#04x}")
200            }
201            DecodeErrorKind::UnexpectedByte { expected, found } => {
202                write!(f, "expected {expected:#04x}, found {found:#04x}")
203            }
204            DecodeErrorKind::SectionSizeMismatch { expected, consumed } => {
205                write!(
206                    f,
207                    "section size mismatch: declared {expected} bytes, consumed {consumed}"
208                )
209            }
210        }
211    }
212}
213
214#[cfg(feature = "std")]
215impl std::error::Error for DecodeError {}
216
217// For no_std with core::error::Error (stabilized in Rust 1.81+)
218#[cfg(not(feature = "std"))]
219impl core::error::Error for DecodeError {}