Skip to main content

baedeker_core/binary/
tablesec.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Table section parsing.
5//!
6//! Decodes defined tables from the table section.
7//! See [Spec §5.5.6](https://webassembly.github.io/spec/core/binary/modules.html#table-section).
8
9use alloc::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_ref_type_with_first_byte,
15};
16use crate::error::{ByteOffset, DecodeContext, DecodeError, DecodeErrorKind};
17use crate::types::{Limits, RefType, TableType};
18
19pub fn parse_table_section(section: &RawSection<'_>) -> Result<Vec<TableType>, DecodeError> {
20    let mut cursor = Cursor::new(section.data);
21    let count = decode_u32_in_section(&mut cursor, section.offset)?;
22
23    let mut tables = Vec::with_capacity(cursor.capacity_hint(count));
24    for _ in 0..count {
25        tables.push(parse_table_type(&mut cursor, section.offset)?);
26    }
27
28    if !cursor.is_empty() {
29        return Err(DecodeError {
30            offset: ByteOffset(section.offset + cursor.position()),
31            context: DecodeContext::TableSection,
32            kind: DecodeErrorKind::SectionSizeMismatch {
33                expected: section.data.len() as u32,
34                consumed: cursor.position() as u32,
35            },
36        });
37    }
38
39    Ok(tables)
40}
41
42fn parse_table_type(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<TableType, DecodeError> {
43    // The 0x40 marker denotes a table with an initializer expression.
44    let first = cursor.read_byte().map_err(|_| DecodeError {
45        offset: ByteOffset(base_offset + cursor.position()),
46        context: DecodeContext::TableSection,
47        kind: DecodeErrorKind::UnexpectedEof,
48    })?;
49    if first == 0x40 {
50        // The 0x40 0x00 marker pair denotes a table with an initializer
51        // expression: reftype, limits, then a const expr.
52        let second = cursor.read_byte().map_err(|_| DecodeError {
53            offset: ByteOffset(base_offset + cursor.position()),
54            context: DecodeContext::TableSection,
55            kind: DecodeErrorKind::UnexpectedEof,
56        })?;
57        if second != 0x00 {
58            return Err(DecodeError {
59                offset: ByteOffset(base_offset + cursor.position() - 1),
60                context: DecodeContext::TableSection,
61                kind: DecodeErrorKind::UnknownRefType { byte: second },
62            });
63        }
64        let elem = parse_ref_type(cursor, base_offset)?;
65        let limits = parse_limits(cursor, base_offset)?;
66        // The initializer is a const expr terminated by `end` (0x0B),
67        // decoded instruction-wise so immediate payloads can't confuse the
68        // terminator scan.
69        let expr_start = cursor.position();
70        let mut block_depth = 0usize;
71        loop {
72            let instr = crate::binary::instr::decode_instr(cursor, base_offset)?;
73            match instr {
74                crate::binary::instr::Instr::Block(_)
75                | crate::binary::instr::Instr::Loop(_)
76                | crate::binary::instr::Instr::If(_) => block_depth += 1,
77                crate::binary::instr::Instr::End => {
78                    if block_depth == 0 {
79                        break;
80                    }
81                    block_depth -= 1;
82                }
83                _ => {}
84            }
85        }
86        let init = cursor.original()[expr_start..cursor.position()].to_vec();
87        return Ok(TableType {
88            elem,
89            limits,
90            init: Some(init),
91        });
92    }
93    let elem = parse_ref_type_with_first_byte(
94        cursor,
95        first,
96        base_offset + cursor.position() - 1,
97        DecodeContext::TableSection,
98    )?;
99    let limits = parse_limits(cursor, base_offset)?;
100    Ok(TableType {
101        elem,
102        limits,
103        init: None,
104    })
105}
106
107fn parse_ref_type(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<RefType, DecodeError> {
108    parse_binary_ref_type(cursor, base_offset, DecodeContext::TableSection)
109}
110
111fn parse_limits(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<Limits, DecodeError> {
112    let tag_offset = cursor.position();
113    let tag = cursor.read_byte().map_err(|_| DecodeError {
114        offset: ByteOffset(base_offset + tag_offset),
115        context: DecodeContext::TableSection,
116        kind: DecodeErrorKind::UnexpectedEof,
117    })?;
118
119    match tag {
120        0x00 => {
121            let min = decode_u32_in_section(cursor, base_offset)?;
122            Ok(Limits { min, max: None })
123        }
124        0x01 => {
125            let min = decode_u32_in_section(cursor, base_offset)?;
126            let max = decode_u32_in_section(cursor, base_offset)?;
127            Ok(Limits {
128                min,
129                max: Some(max),
130            })
131        }
132        _ => Err(DecodeError {
133            offset: ByteOffset(base_offset + tag_offset),
134            context: DecodeContext::TableSection,
135            kind: DecodeErrorKind::UnexpectedByte {
136                expected: 0x00,
137                found: tag,
138            },
139        }),
140    }
141}
142
143fn decode_u32_in_section(cursor: &mut Cursor<'_>, base_offset: usize) -> Result<u32, DecodeError> {
144    leb128::decode_u32(cursor).map_err(|mut e| {
145        e.context = DecodeContext::TableSection;
146        e.offset = ByteOffset(base_offset + e.offset.0);
147        e
148    })
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::binary::section::SectionId;
155
156    fn raw_table_section(data: &[u8]) -> RawSection<'_> {
157        RawSection {
158            id: SectionId::Table,
159            offset: 22,
160            data,
161        }
162    }
163
164    #[test]
165    fn parse_empty_table_section() {
166        let section = raw_table_section(&[0x00]);
167        let tables = parse_table_section(&section).unwrap();
168        assert!(tables.is_empty());
169    }
170
171    #[test]
172    fn parse_single_funcref_table() {
173        let section = raw_table_section(&[0x01, 0x70, 0x00, 0x02]);
174        let tables = parse_table_section(&section).unwrap();
175        assert_eq!(tables.len(), 1);
176        assert_eq!(tables[0].elem, RefType::FuncRef);
177        assert_eq!(tables[0].limits.min, 2);
178        assert_eq!(tables[0].limits.max, None);
179    }
180
181    #[test]
182    fn parse_single_externref_bounded_table() {
183        let section = raw_table_section(&[0x01, 0x6F, 0x01, 0x01, 0x03]);
184        let tables = parse_table_section(&section).unwrap();
185        assert_eq!(tables.len(), 1);
186        assert_eq!(tables[0].elem, RefType::ExternRef);
187        assert_eq!(tables[0].limits.min, 1);
188        assert_eq!(tables[0].limits.max, Some(3));
189    }
190
191    #[test]
192    fn reject_unknown_ref_type() {
193        let section = raw_table_section(&[0x01, 0x6E, 0x00, 0x01]);
194        let err = parse_table_section(&section).unwrap_err();
195        assert!(matches!(
196            err.kind,
197            DecodeErrorKind::UnknownRefType { byte: 0x6E }
198        ));
199    }
200
201    #[test]
202    fn parse_typed_function_reference_table() {
203        let section = raw_table_section(&[
204            0x01, // one table
205            0x63, 0x00, // (ref null type 0)
206            0x00, 0x01, // min 1
207        ]);
208        let tables = parse_table_section(&section).unwrap();
209
210        assert_eq!(
211            tables[0].elem,
212            RefType::concrete(true, crate::types::TypeIdx(0))
213        );
214        assert_eq!(tables[0].limits.min, 1);
215        assert_eq!(tables[0].limits.max, None);
216    }
217
218    #[test]
219    fn reject_invalid_limits_tag() {
220        let section = raw_table_section(&[0x01, 0x70, 0x02, 0x01]);
221        let err = parse_table_section(&section).unwrap_err();
222        assert!(matches!(
223            err.kind,
224            DecodeErrorKind::UnexpectedByte {
225                expected: 0x00,
226                found: 0x02,
227            }
228        ));
229    }
230}