Skip to main content

baedeker_core/binary/
importsec.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Import section parsing.
5//!
6//! Decodes imports and their descriptors from the import section.
7//! See [Spec §5.5.5](https://webassembly.github.io/spec/core/binary/modules.html#import-section).
8
9use alloc::{borrow::ToOwned, string::String, vec::Vec};
10
11use crate::binary::leb128::{self, Cursor};
12use crate::binary::section::RawSection;
13use crate::binary::typeparser::{
14    parse_ref_type as parse_binary_ref_type, parse_val_type as parse_binary_val_type,
15};
16use crate::error::{ByteOffset, DecodeContext, DecodeError, DecodeErrorKind};
17use crate::types::{
18    GlobalType, Import, ImportDesc, Limits, MemType, Mutability, RefType, TableType, TypeIdx,
19    ValType,
20};
21
22pub fn parse_import_section(section: &RawSection<'_>) -> Result<Vec<Import>, DecodeError> {
23    let mut cursor = Cursor::new(section.data);
24    let count = decode_u32_in_section(&mut cursor, section.offset, DecodeContext::ImportSection)?;
25
26    let mut imports = Vec::with_capacity(cursor.capacity_hint(count));
27    for _ in 0..count {
28        let module = parse_name(&mut cursor, section.offset)?;
29        let name = parse_name(&mut cursor, section.offset)?;
30        let desc = parse_import_desc(&mut cursor, section.offset)?;
31        imports.push(Import { module, name, desc });
32    }
33
34    if !cursor.is_empty() {
35        return Err(DecodeError {
36            offset: ByteOffset(section.offset + cursor.position()),
37            context: DecodeContext::ImportSection,
38            kind: DecodeErrorKind::SectionSizeMismatch {
39                expected: section.data.len() as u32,
40                consumed: cursor.position() as u32,
41            },
42        });
43    }
44
45    Ok(imports)
46}
47
48fn parse_import_desc(
49    cursor: &mut Cursor<'_>,
50    base_offset: usize,
51) -> Result<ImportDesc, DecodeError> {
52    let offset = cursor.position();
53    let byte = cursor.read_byte().map_err(|_| DecodeError {
54        offset: ByteOffset(base_offset + offset),
55        context: DecodeContext::ImportSection,
56        kind: DecodeErrorKind::UnexpectedEof,
57    })?;
58
59    match byte {
60        0x00 => {
61            let type_idx =
62                decode_u32_in_section(cursor, base_offset, DecodeContext::ImportSection)?;
63            Ok(ImportDesc::Func(TypeIdx(type_idx)))
64        }
65        0x01 => Ok(ImportDesc::Table(parse_table_type(cursor, base_offset)?)),
66        0x02 => Ok(ImportDesc::Mem(MemType {
67            limits: parse_limits(cursor, base_offset)?,
68        })),
69        0x03 => Ok(ImportDesc::Global(parse_global_type(cursor, base_offset)?)),
70        _ => Err(DecodeError {
71            offset: ByteOffset(base_offset + offset),
72            context: DecodeContext::ImportSection,
73            kind: DecodeErrorKind::UnknownImportDesc { byte },
74        }),
75    }
76}
77
78fn parse_table_type(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<TableType, DecodeError> {
79    let elem = parse_ref_type(cursor, base_offset)?;
80    let limits = parse_limits(cursor, base_offset)?;
81    Ok(TableType {
82        elem,
83        limits,
84        init: None,
85    })
86}
87
88fn parse_global_type(
89    cursor: &mut Cursor<'_>,
90    base_offset: usize,
91) -> Result<GlobalType, DecodeError> {
92    let val_type = parse_val_type(cursor, base_offset)?;
93    let mutability = parse_mutability(cursor, base_offset)?;
94    Ok(GlobalType {
95        val_type,
96        mutability,
97    })
98}
99
100fn parse_limits(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<Limits, DecodeError> {
101    let tag_offset = cursor.position();
102    let tag = cursor.read_byte().map_err(|_| DecodeError {
103        offset: ByteOffset(base_offset + tag_offset),
104        context: DecodeContext::ImportSection,
105        kind: DecodeErrorKind::UnexpectedEof,
106    })?;
107
108    match tag {
109        0x00 => {
110            let min = decode_u32_in_section(cursor, base_offset, DecodeContext::ImportSection)?;
111            Ok(Limits { min, max: None })
112        }
113        0x01 => {
114            let min = decode_u32_in_section(cursor, base_offset, DecodeContext::ImportSection)?;
115            let max = decode_u32_in_section(cursor, base_offset, DecodeContext::ImportSection)?;
116            Ok(Limits {
117                min,
118                max: Some(max),
119            })
120        }
121        _ => Err(DecodeError {
122            offset: ByteOffset(base_offset + tag_offset),
123            context: DecodeContext::ImportSection,
124            kind: DecodeErrorKind::UnexpectedByte {
125                expected: 0x00,
126                found: tag,
127            },
128        }),
129    }
130}
131
132fn parse_name(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<String, DecodeError> {
133    let length = decode_u32_in_section(cursor, base_offset, DecodeContext::ImportSection)? as usize;
134    let offset = cursor.position();
135    let bytes = cursor.read_bytes(length).map_err(|_| DecodeError {
136        offset: ByteOffset(base_offset + offset),
137        context: DecodeContext::ImportSection,
138        kind: DecodeErrorKind::UnexpectedEof,
139    })?;
140
141    core::str::from_utf8(bytes)
142        .map(str::to_owned)
143        .map_err(|_| DecodeError {
144            offset: ByteOffset(base_offset + offset),
145            context: DecodeContext::ImportSection,
146            kind: DecodeErrorKind::InvalidUtf8,
147        })
148}
149
150fn parse_val_type(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<ValType, DecodeError> {
151    parse_binary_val_type(cursor, base_offset, DecodeContext::ImportSection)
152}
153
154fn parse_ref_type(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<RefType, DecodeError> {
155    parse_binary_ref_type(cursor, base_offset, DecodeContext::ImportSection)
156}
157
158fn parse_mutability(
159    cursor: &mut Cursor<'_>,
160    base_offset: usize,
161) -> Result<Mutability, DecodeError> {
162    let offset = cursor.position();
163    let byte = cursor.read_byte().map_err(|_| DecodeError {
164        offset: ByteOffset(base_offset + offset),
165        context: DecodeContext::ImportSection,
166        kind: DecodeErrorKind::UnexpectedEof,
167    })?;
168
169    match byte {
170        0x00 => Ok(Mutability::Const),
171        0x01 => Ok(Mutability::Var),
172        _ => Err(DecodeError {
173            offset: ByteOffset(base_offset + offset),
174            context: DecodeContext::ImportSection,
175            kind: DecodeErrorKind::InvalidMutability { byte },
176        }),
177    }
178}
179
180fn decode_u32_in_section(
181    cursor: &mut Cursor<'_>,
182    base_offset: usize,
183    context: DecodeContext,
184) -> Result<u32, DecodeError> {
185    leb128::decode_u32(cursor).map_err(|mut e| {
186        e.context = context;
187        e.offset = ByteOffset(base_offset + e.offset.0);
188        e
189    })
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::binary::section::SectionId;
196    use crate::types::NumType;
197
198    fn raw_import_section(data: &[u8]) -> RawSection<'_> {
199        RawSection {
200            id: SectionId::Import,
201            offset: 20,
202            data,
203        }
204    }
205
206    #[test]
207    fn parse_empty_import_section() {
208        let section = raw_import_section(&[0x00]);
209        let imports = parse_import_section(&section).unwrap();
210        assert!(imports.is_empty());
211    }
212
213    #[test]
214    fn parse_function_and_memory_imports() {
215        let section = raw_import_section(&[
216            0x02, 0x03, b'e', b'n', b'v', 0x05, b'p', b'r', b'i', b'n', b't', 0x00, 0x01, 0x03,
217            b'e', b'n', b'v', 0x03, b'm', b'e', b'm', 0x02, 0x01, 0x01, 0x02,
218        ]);
219
220        let imports = parse_import_section(&section).unwrap();
221        assert_eq!(imports.len(), 2);
222        assert_eq!(imports[0].module, "env");
223        assert_eq!(imports[0].name, "print");
224        assert_eq!(imports[0].desc, ImportDesc::Func(TypeIdx(1)));
225        assert_eq!(imports[1].name, "mem");
226        assert_eq!(
227            imports[1].desc,
228            ImportDesc::Mem(MemType {
229                limits: Limits {
230                    min: 1,
231                    max: Some(2),
232                },
233            })
234        );
235    }
236
237    #[test]
238    fn parse_global_import() {
239        let section =
240            raw_import_section(&[0x01, 0x03, b'e', b'n', b'v', 0x01, b'g', 0x03, 0x7F, 0x01]);
241        let imports = parse_import_section(&section).unwrap();
242        assert_eq!(
243            imports[0].desc,
244            ImportDesc::Global(GlobalType {
245                val_type: ValType::Num(NumType::I32),
246                mutability: Mutability::Var,
247            })
248        );
249    }
250
251    #[test]
252    fn reject_unknown_import_descriptor() {
253        let section = raw_import_section(&[0x01, 0x01, b'm', 0x01, b'n', 0x09]);
254        let err = parse_import_section(&section).unwrap_err();
255        assert_eq!(err.kind, DecodeErrorKind::UnknownImportDesc { byte: 0x09 });
256    }
257
258    #[test]
259    fn reject_invalid_utf8_name() {
260        let section = raw_import_section(&[0x01, 0x01, 0xFF, 0x01, b'n', 0x00, 0x00]);
261        let err = parse_import_section(&section).unwrap_err();
262        assert_eq!(err.kind, DecodeErrorKind::InvalidUtf8);
263    }
264
265    #[test]
266    fn reject_invalid_mutability() {
267        let section = raw_import_section(&[0x01, 0x01, b'm', 0x01, b'g', 0x03, 0x7F, 0x02]);
268        let err = parse_import_section(&section).unwrap_err();
269        assert_eq!(err.kind, DecodeErrorKind::InvalidMutability { byte: 0x02 });
270    }
271}