Skip to main content

baedeker_core/binary/
module.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Top-level WASM module decoding.
5//!
6//! Produces a `Module` — a parsed but not yet validated representation of a
7//! WASM binary. At this phase, section contents are stored as raw byte spans.
8//! Future phases will add full section content parsing.
9//!
10//! See [Spec §5.5.1](https://webassembly.github.io/spec/core/binary/modules.html).
11
12use alloc::vec::Vec;
13
14use crate::binary::codesec;
15use crate::binary::datasec;
16use crate::binary::elemsec;
17use crate::binary::exportsec;
18use crate::binary::functionsec;
19use crate::binary::globalsec;
20use crate::binary::importsec;
21use crate::binary::leb128::Cursor;
22use crate::binary::memorysec;
23use crate::binary::section::{self, RawSection, SectionId};
24use crate::binary::startsec;
25use crate::binary::tablesec;
26use crate::binary::typesec;
27use crate::error::{ByteOffset, DecodeError, DecodeErrorKind};
28use crate::types::{
29    CodeBody, DataSegment, ElementSegment, Export, FuncIdx, FuncType, Global, Import, MemType,
30    TableType, TypeIdx,
31};
32
33/// A parsed WASM module. Contains the raw section data segmented by type.
34///
35/// At Phase 0, section bodies are stored as raw bytes. Later phases will
36/// parse type entries, imports, function bodies, etc.
37#[derive(Debug)]
38pub struct Module<'a> {
39    /// All sections in the order they appeared, as raw byte spans.
40    pub sections: Vec<RawSection<'a>>,
41    /// Decoded function signatures from the type section.
42    pub types: Vec<FuncType>,
43    /// Imported items declared by the module.
44    pub imports: Vec<Import>,
45    /// Exported items declared by the module.
46    pub exports: Vec<Export>,
47    /// Type indices for module-defined (non-imported) functions.
48    pub functions: Vec<TypeIdx>,
49    /// Defined tables from the table section.
50    pub tables: Vec<TableType>,
51    /// Defined element segments from the element section.
52    pub elements: Vec<ElementSegment<'a>>,
53    /// Defined globals from the global section.
54    pub globals: Vec<Global<'a>>,
55    /// Defined memories from the memory section.
56    pub memories: Vec<MemType>,
57    /// Defined data segments from the data section.
58    pub data: Vec<DataSegment<'a>>,
59    /// Optional declared start function.
60    pub start: Option<FuncIdx>,
61    /// Optional declared data count.
62    pub data_count: Option<u32>,
63    /// Function bodies from the code section.
64    pub codes: Vec<CodeBody<'a>>,
65}
66
67impl<'a> Module<'a> {
68    /// Decode a WASM binary into a `Module`.
69    ///
70    /// This validates the preamble (magic + version), parses section boundaries,
71    /// and checks section ordering. It does NOT validate section contents.
72    pub fn decode(bytes: &'a [u8]) -> Result<Self, DecodeError> {
73        let mut cursor = Cursor::new(bytes);
74
75        section::parse_preamble(&mut cursor)?;
76        let sections = section::parse_sections(&mut cursor)?;
77        let types = match sections.iter().find(|s| s.id == SectionId::Type) {
78            Some(section) => typesec::parse_type_section(section)?,
79            None => Vec::new(),
80        };
81        let imports = match sections.iter().find(|s| s.id == SectionId::Import) {
82            Some(section) => importsec::parse_import_section(section)?,
83            None => Vec::new(),
84        };
85        let exports = match sections.iter().find(|s| s.id == SectionId::Export) {
86            Some(section) => exportsec::parse_export_section(section)?,
87            None => Vec::new(),
88        };
89        let functions = match sections.iter().find(|s| s.id == SectionId::Function) {
90            Some(section) => functionsec::parse_function_section(section)?,
91            None => Vec::new(),
92        };
93        let tables = match sections.iter().find(|s| s.id == SectionId::Table) {
94            Some(section) => tablesec::parse_table_section(section)?,
95            None => Vec::new(),
96        };
97        let elements = match sections.iter().find(|s| s.id == SectionId::Element) {
98            Some(section) => elemsec::parse_element_section(section)?,
99            None => Vec::new(),
100        };
101        let globals = match sections.iter().find(|s| s.id == SectionId::Global) {
102            Some(section) => globalsec::parse_global_section(section)?,
103            None => Vec::new(),
104        };
105        let memories = match sections.iter().find(|s| s.id == SectionId::Memory) {
106            Some(section) => memorysec::parse_memory_section(section)?,
107            None => Vec::new(),
108        };
109        let data = match sections.iter().find(|s| s.id == SectionId::Data) {
110            Some(section) => datasec::parse_data_section(section)?,
111            None => Vec::new(),
112        };
113        let start = match sections.iter().find(|s| s.id == SectionId::Start) {
114            Some(section) => Some(startsec::parse_start_section(section)?),
115            None => None,
116        };
117        let data_count = match sections.iter().find(|s| s.id == SectionId::DataCount) {
118            Some(section) => Some(datasec::parse_data_count_section(section)?),
119            None => None,
120        };
121        let codes = match sections.iter().find(|s| s.id == SectionId::Code) {
122            Some(section) => codesec::parse_code_section(section)?,
123            None => Vec::new(),
124        };
125
126        if functions.len() != codes.len() {
127            return Err(DecodeError {
128                offset: ByteOffset(0),
129                context: crate::error::DecodeContext::CodeSection,
130                kind: DecodeErrorKind::FunctionCodeLengthMismatch {
131                    functions: functions.len() as u32,
132                    codes: codes.len() as u32,
133                },
134            });
135        }
136
137        Ok(Module {
138            sections,
139            types,
140            imports,
141            exports,
142            functions,
143            tables,
144            elements,
145            globals,
146            memories,
147            data,
148            start,
149            data_count,
150            codes,
151        })
152    }
153
154    /// Get the first section with the given ID, if present.
155    pub fn section(&self, id: SectionId) -> Option<&RawSection<'a>> {
156        self.sections.iter().find(|s| s.id == id)
157    }
158
159    /// Function signatures declared in the type section.
160    pub fn types(&self) -> &[FuncType] {
161        &self.types
162    }
163
164    /// Imported items declared by the module.
165    pub fn imports(&self) -> &[Import] {
166        &self.imports
167    }
168
169    /// Exported items declared by the module.
170    pub fn exports(&self) -> &[Export] {
171        &self.exports
172    }
173
174    /// Type indices for module-defined functions.
175    pub fn functions(&self) -> &[TypeIdx] {
176        &self.functions
177    }
178
179    /// Defined tables from the table section.
180    pub fn tables(&self) -> &[TableType] {
181        &self.tables
182    }
183
184    /// Defined element segments from the element section.
185    pub fn elements(&self) -> &[ElementSegment<'a>] {
186        &self.elements
187    }
188
189    /// Number of imported functions in the function index space.
190    pub fn imported_function_count(&self) -> usize {
191        self.imports
192            .iter()
193            .filter(|import| matches!(import.desc, crate::types::ImportDesc::Func(_)))
194            .count()
195    }
196
197    /// Defined globals from the global section.
198    pub fn globals(&self) -> &[Global<'a>] {
199        &self.globals
200    }
201
202    /// Defined memories from the memory section.
203    pub fn memories(&self) -> &[MemType] {
204        &self.memories
205    }
206
207    /// Defined data segments from the data section.
208    pub fn data(&self) -> &[DataSegment<'a>] {
209        &self.data
210    }
211
212    /// Declared start function, if present.
213    pub fn start(&self) -> Option<FuncIdx> {
214        self.start
215    }
216
217    /// Declared data count, if present.
218    pub fn data_count(&self) -> Option<u32> {
219        self.data_count
220    }
221
222    /// Function bodies from the code section.
223    pub fn codes(&self) -> &[CodeBody<'a>] {
224        &self.codes
225    }
226
227    /// Iterate over all custom sections.
228    pub fn custom_sections(&self) -> impl Iterator<Item = &RawSection<'a>> {
229        self.sections.iter().filter(|s| s.id == SectionId::Custom)
230    }
231
232    /// Summary of the module's section layout for display.
233    pub fn section_summary(&self) -> Vec<(SectionId, usize, usize)> {
234        self.sections
235            .iter()
236            .map(|s| (s.id, s.offset, s.data.len()))
237            .collect()
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use alloc::vec;
244
245    use super::*;
246
247    #[test]
248    fn decode_minimal_module() {
249        let bytes = [
250            0x00, 0x61, 0x73, 0x6D, // magic
251            0x01, 0x00, 0x00, 0x00, // version
252        ];
253        let module = Module::decode(&bytes).unwrap();
254        assert!(module.sections.is_empty());
255        assert!(module.types.is_empty());
256        assert!(module.imports.is_empty());
257        assert!(module.exports.is_empty());
258        assert!(module.functions.is_empty());
259        assert!(module.tables.is_empty());
260        assert!(module.elements.is_empty());
261        assert!(module.globals.is_empty());
262        assert!(module.memories.is_empty());
263        assert!(module.data.is_empty());
264        assert!(module.start.is_none());
265        assert!(module.data_count.is_none());
266        assert!(module.codes.is_empty());
267    }
268
269    #[test]
270    fn decode_module_with_sections() {
271        let bytes = [
272            0x00, 0x61, 0x73, 0x6D, // magic
273            0x01, 0x00, 0x00, 0x00, // version
274            0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // type section: 1 functype () -> ()
275            0x03, 0x02, 0x01, 0x00, // function section: 1 function referencing type 0
276            0x0A, 0x04, 0x01, 0x02, 0x00, 0x0B, // code section: 1 empty body
277        ];
278        let module = Module::decode(&bytes).unwrap();
279        assert_eq!(module.sections.len(), 3);
280        assert_eq!(module.sections[0].id, SectionId::Type);
281        assert_eq!(module.sections[1].id, SectionId::Function);
282        assert_eq!(module.sections[2].id, SectionId::Code);
283        assert_eq!(module.types.len(), 1);
284        assert!(module.types[0].params.is_empty());
285        assert!(module.types[0].results.is_empty());
286        assert!(module.exports.is_empty());
287        assert_eq!(module.functions, vec![TypeIdx(0)]);
288        assert!(module.tables.is_empty());
289        assert!(module.elements.is_empty());
290        assert!(module.globals.is_empty());
291        assert!(module.memories.is_empty());
292        assert!(module.data.is_empty());
293        assert!(module.start.is_none());
294        assert!(module.data_count.is_none());
295        assert_eq!(module.codes.len(), 1);
296        assert!(module.codes[0].locals.is_empty());
297        assert_eq!(module.codes[0].body, &[0x0B]);
298    }
299
300    #[test]
301    fn section_lookup() {
302        let bytes = [
303            0x00, 0x61, 0x73, 0x6D, // magic
304            0x01, 0x00, 0x00, 0x00, // version
305            0x01, 0x01, 0x00, // type section: zero entries
306        ];
307        let module = Module::decode(&bytes).unwrap();
308        assert!(module.section(SectionId::Type).is_some());
309        assert!(module.section(SectionId::Import).is_none());
310        assert_eq!(module.types().len(), 0);
311        assert!(module.imports().is_empty());
312        assert!(module.exports().is_empty());
313        assert!(module.functions().is_empty());
314        assert!(module.tables().is_empty());
315        assert!(module.elements().is_empty());
316        assert!(module.globals().is_empty());
317        assert!(module.memories().is_empty());
318        assert!(module.data().is_empty());
319        assert!(module.start().is_none());
320        assert!(module.data_count().is_none());
321        assert!(module.codes().is_empty());
322    }
323
324    #[test]
325    fn reject_truncated_binary() {
326        let bytes = [0x00, 0x61]; // truncated magic
327        let err = Module::decode(&bytes).unwrap_err();
328        assert!(matches!(
329            err.kind,
330            crate::error::DecodeErrorKind::UnexpectedEof
331        ));
332    }
333
334    #[test]
335    fn reject_bad_magic() {
336        let bytes = [
337            0xDE, 0xAD, 0xBE, 0xEF, // wrong magic
338            0x01, 0x00, 0x00, 0x00, // version
339        ];
340        let err = Module::decode(&bytes).unwrap_err();
341        assert!(matches!(
342            err.kind,
343            crate::error::DecodeErrorKind::InvalidMagic
344        ));
345    }
346
347    #[test]
348    fn reject_gc_rec_type_group_boundary() {
349        let bytes =
350            include_bytes!("../../../baedeker-testdata/spec/invalid-decode/gc-rec-type-group.wasm");
351        let err = Module::decode(bytes).unwrap_err();
352        assert_eq!(err.offset, ByteOffset(11));
353        assert_eq!(err.context, crate::error::DecodeContext::TypeSection);
354        assert!(matches!(
355            err.kind,
356            DecodeErrorKind::UnexpectedByte {
357                expected: 0x60,
358                found: 0x4E,
359            }
360        ));
361    }
362
363    #[test]
364    fn reject_gc_sub_type_definition_boundary() {
365        let bytes = include_bytes!(
366            "../../../baedeker-testdata/spec/invalid-decode/gc-sub-type-definition.wasm",
367        );
368        let err = Module::decode(bytes).unwrap_err();
369        assert_eq!(err.offset, ByteOffset(11));
370        assert_eq!(err.context, crate::error::DecodeContext::TypeSection);
371        assert!(matches!(
372            err.kind,
373            DecodeErrorKind::UnexpectedByte {
374                expected: 0x60,
375                found: 0x50,
376            }
377        ));
378    }
379
380    #[test]
381    fn reject_gc_struct_type_definition_boundary() {
382        let bytes = include_bytes!(
383            "../../../baedeker-testdata/spec/invalid-decode/gc-struct-type-definition.wasm",
384        );
385        let err = Module::decode(bytes).unwrap_err();
386        assert_eq!(err.offset, ByteOffset(11));
387        assert_eq!(err.context, crate::error::DecodeContext::TypeSection);
388        assert!(matches!(
389            err.kind,
390            DecodeErrorKind::UnexpectedByte {
391                expected: 0x60,
392                found: 0x5F,
393            }
394        ));
395    }
396
397    #[test]
398    fn reject_gc_array_type_definition_boundary() {
399        let bytes = include_bytes!(
400            "../../../baedeker-testdata/spec/invalid-decode/gc-array-type-definition.wasm",
401        );
402        let err = Module::decode(bytes).unwrap_err();
403        assert_eq!(err.offset, ByteOffset(11));
404        assert_eq!(err.context, crate::error::DecodeContext::TypeSection);
405        assert!(matches!(
406            err.kind,
407            DecodeErrorKind::UnexpectedByte {
408                expected: 0x60,
409                found: 0x5E,
410            }
411        ));
412    }
413
414    #[test]
415    fn decode_empty_fixture() {
416        let bytes = baedeker_testdata::fixture_bytes("empty");
417        let module = Module::decode(&bytes).unwrap();
418        // Minimal module has at least a valid preamble; may have custom sections
419        // from the Rust toolchain but no required non-custom sections.
420        for s in &module.sections {
421            // Should parse without error regardless of section content.
422            let _ = s.id.name();
423        }
424    }
425
426    #[test]
427    fn decode_add_fixture() {
428        let bytes = baedeker_testdata::fixture_bytes("add");
429        let module = Module::decode(&bytes).unwrap();
430        assert!(
431            !module.types().is_empty(),
432            "add.wasm should decode at least one function type"
433        );
434        assert!(
435            !module.functions().is_empty(),
436            "add.wasm should decode at least one function declaration"
437        );
438        assert!(
439            !module.codes().is_empty(),
440            "add.wasm should decode at least one function body"
441        );
442        assert_eq!(module.functions().len(), module.codes().len());
443
444        // A cdylib with an exported function must have type, function, and code sections.
445        assert!(
446            module.section(SectionId::Type).is_some(),
447            "add.wasm missing type section"
448        );
449        assert!(
450            module.section(SectionId::Function).is_some(),
451            "add.wasm missing function section"
452        );
453        assert!(
454            module.section(SectionId::Code).is_some(),
455            "add.wasm missing code section"
456        );
457        assert!(
458            module.section(SectionId::Export).is_some(),
459            "add.wasm missing export section"
460        );
461        assert!(
462            !module.exports().is_empty(),
463            "add.wasm export section should decode exports"
464        );
465    }
466
467    #[test]
468    fn decode_memory_fixture() {
469        let bytes = baedeker_testdata::fixture_bytes("memory");
470        let module = Module::decode(&bytes).unwrap();
471
472        // Module with static memory should have a memory or data section.
473        assert!(
474            module.section(SectionId::Memory).is_some()
475                || module.section(SectionId::Data).is_some(),
476            "memory.wasm missing memory/data section"
477        );
478        if module.section(SectionId::Memory).is_some() {
479            assert!(
480                !module.memories().is_empty(),
481                "memory.wasm memory section should decode memory types"
482            );
483        }
484    }
485
486    #[test]
487    fn decode_module_with_global_section() {
488        let bytes = [
489            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x06, 0x06, 0x01, 0x7F, 0x00, 0x41,
490            0x2A, 0x0B,
491        ];
492        let module = Module::decode(&bytes).unwrap();
493        assert_eq!(module.globals().len(), 1);
494        assert_eq!(module.globals()[0].init_expr, &[0x41, 0x2A, 0x0B]);
495    }
496
497    #[test]
498    fn decode_module_with_table_section() {
499        let bytes = [
500            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x04, 0x04, 0x01, 0x70, 0x00, 0x02,
501        ];
502        let module = Module::decode(&bytes).unwrap();
503        assert_eq!(module.tables().len(), 1);
504        assert_eq!(module.tables()[0].elem, crate::types::RefType::FuncRef);
505        assert_eq!(module.tables()[0].limits.min, 2);
506        assert_eq!(module.tables()[0].limits.max, None);
507    }
508
509    #[test]
510    fn decode_module_with_memory_section() {
511        let bytes = [
512            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, 0x02,
513        ];
514        let module = Module::decode(&bytes).unwrap();
515        assert_eq!(module.memories().len(), 1);
516        assert_eq!(module.memories()[0].limits.min, 2);
517        assert_eq!(module.memories()[0].limits.max, None);
518    }
519
520    #[test]
521    fn decode_module_with_data_section() {
522        let bytes = [
523            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, 0x01, 0x0B,
524            0x08, 0x01, 0x00, 0x41, 0x00, 0x0B, 0x02, 0xAA, 0xBB, 0x0C, 0x01, 0x01,
525        ];
526        let module = Module::decode(&bytes).unwrap();
527        assert_eq!(module.data().len(), 1);
528        assert_eq!(module.data()[0].init, &[0xAA, 0xBB]);
529        assert_eq!(module.data_count(), Some(1));
530    }
531
532    #[test]
533    fn decode_module_with_export_section() {
534        let bytes = [
535            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x07, 0x07, 0x01, 0x03, b'a', b'd',
536            b'd', 0x00, 0x00,
537        ];
538        let module = Module::decode(&bytes).unwrap();
539        assert_eq!(module.exports().len(), 1);
540        assert_eq!(module.exports()[0].name, "add");
541        assert_eq!(
542            module.exports()[0].desc,
543            crate::types::ExportDesc::Func(crate::types::FuncIdx(0))
544        );
545    }
546
547    #[test]
548    fn decode_module_with_start_section() {
549        let bytes = [
550            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x08, 0x01, 0x02,
551        ];
552        let module = Module::decode(&bytes).unwrap();
553        assert_eq!(module.start(), Some(crate::types::FuncIdx(2)));
554    }
555
556    #[test]
557    fn decode_module_with_element_section() {
558        let bytes = [
559            0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x09, 0x08, 0x01, 0x00, 0x41, 0x00,
560            0x0B, 0x02, 0x00, 0x01,
561        ];
562        let module = Module::decode(&bytes).unwrap();
563        assert_eq!(module.elements().len(), 1);
564        assert_eq!(
565            module.elements()[0].elem_type,
566            crate::types::RefType::FuncRef
567        );
568        assert!(matches!(
569            module.elements()[0].mode,
570            crate::types::ElementMode::Active {
571                table: crate::types::TableIdx(0),
572                ..
573            }
574        ));
575    }
576}