Skip to main content

baedeker_core/binary/
exportsec.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Export section parsing.
5//!
6//! Decodes exports and their descriptors from the export section.
7//! See [Spec §5.5.10](https://webassembly.github.io/spec/core/binary/modules.html#export-section).
8
9use alloc::{borrow::ToOwned, string::String, vec::Vec};
10
11use crate::binary::leb128::{self, Cursor};
12use crate::binary::section::RawSection;
13use crate::error::{ByteOffset, DecodeContext, DecodeError, DecodeErrorKind};
14use crate::types::{Export, ExportDesc, FuncIdx, GlobalIdx, MemIdx, TableIdx};
15
16pub fn parse_export_section(section: &RawSection<'_>) -> Result<Vec<Export>, DecodeError> {
17    let mut cursor = Cursor::new(section.data);
18    let count = decode_u32_in_section(&mut cursor, section.offset)?;
19
20    let mut exports = Vec::with_capacity(cursor.capacity_hint(count));
21    for _ in 0..count {
22        let name = parse_name(&mut cursor, section.offset)?;
23        let desc = parse_export_desc(&mut cursor, section.offset)?;
24        exports.push(Export { name, desc });
25    }
26
27    if !cursor.is_empty() {
28        return Err(DecodeError {
29            offset: ByteOffset(section.offset + cursor.position()),
30            context: DecodeContext::ExportSection,
31            kind: DecodeErrorKind::SectionSizeMismatch {
32                expected: section.data.len() as u32,
33                consumed: cursor.position() as u32,
34            },
35        });
36    }
37
38    Ok(exports)
39}
40
41fn parse_name(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<String, DecodeError> {
42    let length = decode_u32_in_section(cursor, base_offset)? as usize;
43    let offset = cursor.position();
44    let bytes = cursor.read_bytes(length).map_err(|_| DecodeError {
45        offset: ByteOffset(base_offset + offset),
46        context: DecodeContext::ExportSection,
47        kind: DecodeErrorKind::UnexpectedEof,
48    })?;
49
50    core::str::from_utf8(bytes)
51        .map(str::to_owned)
52        .map_err(|_| DecodeError {
53            offset: ByteOffset(base_offset + offset),
54            context: DecodeContext::ExportSection,
55            kind: DecodeErrorKind::InvalidUtf8,
56        })
57}
58
59fn parse_export_desc(
60    cursor: &mut Cursor<'_>,
61    base_offset: usize,
62) -> Result<ExportDesc, DecodeError> {
63    let offset = cursor.position();
64    let byte = cursor.read_byte().map_err(|_| DecodeError {
65        offset: ByteOffset(base_offset + offset),
66        context: DecodeContext::ExportSection,
67        kind: DecodeErrorKind::UnexpectedEof,
68    })?;
69
70    let idx = decode_u32_in_section(cursor, base_offset)?;
71    match byte {
72        0x00 => Ok(ExportDesc::Func(FuncIdx(idx))),
73        0x01 => Ok(ExportDesc::Table(TableIdx(idx))),
74        0x02 => Ok(ExportDesc::Mem(MemIdx(idx))),
75        0x03 => Ok(ExportDesc::Global(GlobalIdx(idx))),
76        _ => Err(DecodeError {
77            offset: ByteOffset(base_offset + offset),
78            context: DecodeContext::ExportSection,
79            kind: DecodeErrorKind::UnknownExportDesc { byte },
80        }),
81    }
82}
83
84fn decode_u32_in_section(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<u32, DecodeError> {
85    leb128::decode_u32(cursor).map_err(|mut e| {
86        e.context = DecodeContext::ExportSection;
87        e.offset = ByteOffset(base_offset + e.offset.0);
88        e
89    })
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::binary::section::SectionId;
96
97    fn raw_export_section(data: &[u8]) -> RawSection<'_> {
98        RawSection {
99            id: SectionId::Export,
100            offset: 28,
101            data,
102        }
103    }
104
105    #[test]
106    fn parse_empty_export_section() {
107        let section = raw_export_section(&[0x00]);
108        let exports = parse_export_section(&section).unwrap();
109        assert!(exports.is_empty());
110    }
111
112    #[test]
113    fn parse_function_and_memory_exports() {
114        let section = raw_export_section(&[
115            0x02, 0x03, b'a', b'd', b'd', 0x00, 0x01, 0x03, b'm', b'e', b'm', 0x02, 0x00,
116        ]);
117
118        let exports = parse_export_section(&section).unwrap();
119        assert_eq!(exports.len(), 2);
120        assert_eq!(exports[0].name, "add");
121        assert_eq!(exports[0].desc, ExportDesc::Func(FuncIdx(1)));
122        assert_eq!(exports[1].name, "mem");
123        assert_eq!(exports[1].desc, ExportDesc::Mem(MemIdx(0)));
124    }
125
126    #[test]
127    fn reject_unknown_export_descriptor() {
128        let section = raw_export_section(&[0x01, 0x01, b'x', 0x04, 0x00]);
129        let err = parse_export_section(&section).unwrap_err();
130        assert!(matches!(
131            err.kind,
132            DecodeErrorKind::UnknownExportDesc { byte: 0x04 }
133        ));
134    }
135}