Skip to main content

miden_mast_package/debug_info/
serialization.rs

1//! Serialization and deserialization for the debug_info section.
2
3use alloc::sync::Arc;
4
5use miden_core::{
6    Word,
7    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
8};
9use miden_debug_types::{ColumnNumber, LineNumber};
10
11use super::{
12    DEBUG_FUNCTIONS_VERSION, DEBUG_SOURCES_VERSION, DEBUG_TYPES_VERSION, DebugFieldInfo,
13    DebugFileInfo, DebugFunctionInfo, DebugFunctionsSection, DebugInlinedCallInfo,
14    DebugPrimitiveType, DebugSourcesSection, DebugTypeIdx, DebugTypeInfo, DebugTypesSection,
15    DebugVariableInfo,
16};
17
18// DEBUG TYPES SECTION SERIALIZATION
19// ================================================================================================
20
21impl Serializable for DebugTypesSection {
22    fn write_into<W: ByteWriter>(&self, target: &mut W) {
23        target.write_u8(self.version);
24
25        // Write string table
26        target.write_usize(self.strings.len());
27        for s in &self.strings {
28            s.as_ref().write_into(target);
29        }
30
31        // Write type table
32        target.write_usize(self.types.len());
33        for ty in &self.types {
34            ty.write_into(target);
35        }
36    }
37}
38
39impl Deserializable for DebugTypesSection {
40    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
41        let version = source.read_u8()?;
42        if version != DEBUG_TYPES_VERSION {
43            return Err(DeserializationError::InvalidValue(alloc::format!(
44                "unsupported debug_types version: {version}, expected {DEBUG_TYPES_VERSION}"
45            )));
46        }
47
48        // Manual bounds check required: read_string is a local helper, not Deserializable,
49        // so we can't use read_many_iter. Each string serializes to at least 1 byte (the
50        // varint length prefix), so max_alloc(1) bounds the vector pre-allocation.
51        let strings_len = source.read_usize()?;
52        let max_strings = source.max_alloc(1);
53        if strings_len > max_strings {
54            return Err(DeserializationError::InvalidValue(alloc::format!(
55                "debug_types strings count {strings_len} exceeds budget {max_strings}"
56            )));
57        }
58        let mut strings = alloc::vec::Vec::with_capacity(strings_len);
59        for _ in 0..strings_len {
60            strings.push(read_string(source)?);
61        }
62
63        let types_len = source.read_usize()?;
64        let types = source.read_many_iter(types_len)?.collect::<Result<_, _>>()?;
65
66        Ok(Self { version, strings, types })
67    }
68}
69
70// DEBUG SOURCES SECTION SERIALIZATION
71// ================================================================================================
72
73impl Serializable for DebugSourcesSection {
74    fn write_into<W: ByteWriter>(&self, target: &mut W) {
75        target.write_u8(self.version);
76
77        // Write string table
78        target.write_usize(self.strings.len());
79        for s in &self.strings {
80            s.as_ref().write_into(target);
81        }
82
83        // Write file table
84        target.write_usize(self.files.len());
85        for file in &self.files {
86            file.write_into(target);
87        }
88    }
89}
90
91impl Deserializable for DebugSourcesSection {
92    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
93        let version = source.read_u8()?;
94        if version != DEBUG_SOURCES_VERSION {
95            return Err(DeserializationError::InvalidValue(alloc::format!(
96                "unsupported debug_sources version: {version}, expected {DEBUG_SOURCES_VERSION}"
97            )));
98        }
99
100        // Manual bounds check required: read_string is a local helper, not Deserializable,
101        // so we can't use read_many_iter. Each string serializes to at least 1 byte (the
102        // varint length prefix), so max_alloc(1) bounds the vector pre-allocation.
103        let strings_len = source.read_usize()?;
104        let max_strings = source.max_alloc(1);
105        if strings_len > max_strings {
106            return Err(DeserializationError::InvalidValue(alloc::format!(
107                "debug_sources strings count {strings_len} exceeds budget {max_strings}"
108            )));
109        }
110        let mut strings = alloc::vec::Vec::with_capacity(strings_len);
111        for _ in 0..strings_len {
112            strings.push(read_string(source)?);
113        }
114
115        let files_len = source.read_usize()?;
116        let files = source.read_many_iter(files_len)?.collect::<Result<_, _>>()?;
117
118        Ok(Self { version, strings, files })
119    }
120}
121
122// DEBUG FUNCTIONS SECTION SERIALIZATION
123// ================================================================================================
124
125impl Serializable for DebugFunctionsSection {
126    fn write_into<W: ByteWriter>(&self, target: &mut W) {
127        target.write_u8(self.version);
128
129        // Write string table
130        target.write_usize(self.strings.len());
131        for s in &self.strings {
132            s.as_ref().write_into(target);
133        }
134
135        // Write function table
136        target.write_usize(self.functions.len());
137        for func in &self.functions {
138            func.write_into(target);
139        }
140    }
141}
142
143impl Deserializable for DebugFunctionsSection {
144    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
145        let version = source.read_u8()?;
146        if version != DEBUG_FUNCTIONS_VERSION {
147            return Err(DeserializationError::InvalidValue(alloc::format!(
148                "unsupported debug_functions version: {version}, expected {DEBUG_FUNCTIONS_VERSION}"
149            )));
150        }
151
152        // Manual bounds check required: read_string is a local helper, not Deserializable,
153        // so we can't use read_many_iter. Each string serializes to at least 1 byte (the
154        // varint length prefix), so max_alloc(1) bounds the vector pre-allocation.
155        let strings_len = source.read_usize()?;
156        let max_strings = source.max_alloc(1);
157        if strings_len > max_strings {
158            return Err(DeserializationError::InvalidValue(alloc::format!(
159                "debug_functions strings count {strings_len} exceeds budget {max_strings}"
160            )));
161        }
162        let mut strings = alloc::vec::Vec::with_capacity(strings_len);
163        for _ in 0..strings_len {
164            strings.push(read_string(source)?);
165        }
166
167        let functions_len = source.read_usize()?;
168        let functions = source.read_many_iter(functions_len)?.collect::<Result<_, _>>()?;
169
170        Ok(Self { version, strings, functions })
171    }
172}
173
174// DEBUG TYPE INFO SERIALIZATION
175// ================================================================================================
176
177// Type tags for serialization
178const TYPE_TAG_PRIMITIVE: u8 = 0;
179const TYPE_TAG_POINTER: u8 = 1;
180const TYPE_TAG_ARRAY: u8 = 2;
181const TYPE_TAG_STRUCT: u8 = 3;
182const TYPE_TAG_FUNCTION: u8 = 4;
183const TYPE_TAG_UNKNOWN: u8 = 5;
184
185impl Serializable for DebugTypeInfo {
186    fn write_into<W: ByteWriter>(&self, target: &mut W) {
187        match self {
188            Self::Primitive(prim) => {
189                target.write_u8(TYPE_TAG_PRIMITIVE);
190                target.write_u8(*prim as u8);
191            },
192            Self::Pointer { pointee_type_idx } => {
193                target.write_u8(TYPE_TAG_POINTER);
194                target.write_u32(pointee_type_idx.as_u32());
195            },
196            Self::Array { element_type_idx, count } => {
197                target.write_u8(TYPE_TAG_ARRAY);
198                target.write_u32(element_type_idx.as_u32());
199                target.write_bool(count.is_some());
200                if let Some(count) = count {
201                    target.write_u32(*count);
202                }
203            },
204            Self::Struct { name_idx, size, fields } => {
205                target.write_u8(TYPE_TAG_STRUCT);
206                target.write_u32(*name_idx);
207                target.write_u32(*size);
208                target.write_usize(fields.len());
209                for field in fields {
210                    field.write_into(target);
211                }
212            },
213            Self::Function { return_type_idx, param_type_indices } => {
214                target.write_u8(TYPE_TAG_FUNCTION);
215                target.write_bool(return_type_idx.is_some());
216                if let Some(idx) = return_type_idx {
217                    target.write_u32(idx.as_u32());
218                }
219                target.write_usize(param_type_indices.len());
220                for idx in param_type_indices {
221                    target.write_u32(idx.as_u32());
222                }
223            },
224            Self::Unknown => {
225                target.write_u8(TYPE_TAG_UNKNOWN);
226            },
227        }
228    }
229}
230
231impl Deserializable for DebugTypeInfo {
232    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
233        let tag = source.read_u8()?;
234        match tag {
235            TYPE_TAG_PRIMITIVE => {
236                let prim_tag = source.read_u8()?;
237                let prim = DebugPrimitiveType::from_discriminant(prim_tag).ok_or_else(|| {
238                    DeserializationError::InvalidValue(alloc::format!(
239                        "invalid primitive type tag: {prim_tag}"
240                    ))
241                })?;
242                Ok(Self::Primitive(prim))
243            },
244            TYPE_TAG_POINTER => {
245                let pointee_type_idx = DebugTypeIdx::from(source.read_u32()?);
246                Ok(Self::Pointer { pointee_type_idx })
247            },
248            TYPE_TAG_ARRAY => {
249                let element_type_idx = DebugTypeIdx::from(source.read_u32()?);
250                let has_count = source.read_bool()?;
251                let count = if has_count { Some(source.read_u32()?) } else { None };
252                Ok(Self::Array { element_type_idx, count })
253            },
254            TYPE_TAG_STRUCT => {
255                let name_idx = source.read_u32()?;
256                let size = source.read_u32()?;
257                let fields_len = source.read_usize()?;
258                let fields = source.read_many_iter(fields_len)?.collect::<Result<_, _>>()?;
259                Ok(Self::Struct { name_idx, size, fields })
260            },
261            TYPE_TAG_FUNCTION => {
262                let has_return = source.read_bool()?;
263                let return_type_idx = if has_return {
264                    Some(DebugTypeIdx::from(source.read_u32()?))
265                } else {
266                    None
267                };
268                let param_type_indices = alloc::vec::Vec::<DebugTypeIdx>::read_from(source)?;
269                Ok(Self::Function { return_type_idx, param_type_indices })
270            },
271            TYPE_TAG_UNKNOWN => Ok(Self::Unknown),
272            _ => Err(DeserializationError::InvalidValue(alloc::format!("invalid type tag: {tag}"))),
273        }
274    }
275}
276
277// DEBUG FIELD INFO SERIALIZATION
278// ================================================================================================
279
280impl Serializable for DebugFieldInfo {
281    fn write_into<W: ByteWriter>(&self, target: &mut W) {
282        target.write_u32(self.name_idx);
283        target.write_u32(self.type_idx.as_u32());
284        target.write_u32(self.offset);
285    }
286}
287
288impl Deserializable for DebugFieldInfo {
289    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
290        let name_idx = source.read_u32()?;
291        let type_idx = DebugTypeIdx::from(source.read_u32()?);
292        let offset = source.read_u32()?;
293        Ok(Self { name_idx, type_idx, offset })
294    }
295}
296
297// DEBUG FILE INFO SERIALIZATION
298// ================================================================================================
299
300impl Serializable for DebugFileInfo {
301    fn write_into<W: ByteWriter>(&self, target: &mut W) {
302        target.write_u32(self.path_idx);
303
304        target.write_bool(self.checksum.is_some());
305        if let Some(checksum) = &self.checksum {
306            target.write_bytes(checksum.as_ref());
307        }
308    }
309}
310
311impl Deserializable for DebugFileInfo {
312    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
313        let path_idx = source.read_u32()?;
314
315        let has_checksum = source.read_bool()?;
316        let checksum = if has_checksum {
317            let bytes = source.read_slice(32)?;
318            let mut arr = [0u8; 32];
319            arr.copy_from_slice(bytes);
320            Some(alloc::boxed::Box::new(arr))
321        } else {
322            None
323        };
324
325        Ok(Self { path_idx, checksum })
326    }
327}
328
329// DEBUG FUNCTION INFO SERIALIZATION
330// ================================================================================================
331
332impl Serializable for DebugFunctionInfo {
333    fn write_into<W: ByteWriter>(&self, target: &mut W) {
334        target.write_u32(self.name_idx);
335
336        target.write_bool(self.linkage_name_idx.is_some());
337        if let Some(idx) = self.linkage_name_idx {
338            target.write_u32(idx);
339        }
340
341        target.write_u32(self.file_idx);
342        target.write_u32(self.line.to_u32());
343        target.write_u32(self.column.to_u32());
344
345        target.write_bool(self.type_idx.is_some());
346        if let Some(idx) = self.type_idx {
347            target.write_u32(idx.as_u32());
348        }
349
350        target.write_bool(self.mast_root.is_some());
351        if let Some(root) = &self.mast_root {
352            root.write_into(target);
353        }
354
355        // Write variables
356        target.write_usize(self.variables.len());
357        for var in &self.variables {
358            var.write_into(target);
359        }
360
361        // Write inlined calls
362        target.write_usize(self.inlined_calls.len());
363        for call in &self.inlined_calls {
364            call.write_into(target);
365        }
366    }
367}
368
369impl Deserializable for DebugFunctionInfo {
370    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
371        let name_idx = source.read_u32()?;
372
373        let has_linkage_name = source.read_bool()?;
374        let linkage_name_idx = if has_linkage_name {
375            Some(source.read_u32()?)
376        } else {
377            None
378        };
379
380        let file_idx = source.read_u32()?;
381        let line_raw = source.read_u32()?;
382        let column_raw = source.read_u32()?;
383        let line = LineNumber::new(line_raw).unwrap_or_default();
384        let column = ColumnNumber::new(column_raw).unwrap_or_default();
385
386        let has_type = source.read_bool()?;
387        let type_idx = if has_type {
388            Some(DebugTypeIdx::from(source.read_u32()?))
389        } else {
390            None
391        };
392
393        let has_mast_root = source.read_bool()?;
394        let mast_root = if has_mast_root {
395            Some(Word::read_from(source)?)
396        } else {
397            None
398        };
399
400        // Read variables
401        let vars_len = source.read_usize()?;
402        let variables = source.read_many_iter(vars_len)?.collect::<Result<_, _>>()?;
403
404        // Read inlined calls
405        let calls_len = source.read_usize()?;
406        let inlined_calls = source.read_many_iter(calls_len)?.collect::<Result<_, _>>()?;
407
408        Ok(Self {
409            name_idx,
410            linkage_name_idx,
411            file_idx,
412            line,
413            column,
414            type_idx,
415            mast_root,
416            variables,
417            inlined_calls,
418        })
419    }
420}
421
422// DEBUG VARIABLE INFO SERIALIZATION
423// ================================================================================================
424
425impl Serializable for DebugVariableInfo {
426    fn write_into<W: ByteWriter>(&self, target: &mut W) {
427        target.write_u32(self.name_idx);
428        target.write_u32(self.type_idx.as_u32());
429        target.write_u32(self.arg_index);
430        target.write_u32(self.line.to_u32());
431        target.write_u32(self.column.to_u32());
432        target.write_u32(self.scope_depth);
433    }
434}
435
436impl Deserializable for DebugVariableInfo {
437    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
438        let name_idx = source.read_u32()?;
439        let type_idx = DebugTypeIdx::from(source.read_u32()?);
440        let arg_index = source.read_u32()?;
441        let line_raw = source.read_u32()?;
442        let column_raw = source.read_u32()?;
443        let line = LineNumber::new(line_raw).unwrap_or_default();
444        let column = ColumnNumber::new(column_raw).unwrap_or_default();
445        let scope_depth = source.read_u32()?;
446        Ok(Self {
447            name_idx,
448            type_idx,
449            arg_index,
450            line,
451            column,
452            scope_depth,
453        })
454    }
455}
456
457// DEBUG INLINED CALL INFO SERIALIZATION
458// ================================================================================================
459
460impl Serializable for DebugInlinedCallInfo {
461    fn write_into<W: ByteWriter>(&self, target: &mut W) {
462        target.write_u32(self.callee_idx);
463        target.write_u32(self.file_idx);
464        target.write_u32(self.line.to_u32());
465        target.write_u32(self.column.to_u32());
466    }
467}
468
469impl Deserializable for DebugInlinedCallInfo {
470    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
471        let callee_idx = source.read_u32()?;
472        let file_idx = source.read_u32()?;
473        let line_raw = source.read_u32()?;
474        let column_raw = source.read_u32()?;
475        let line = LineNumber::new(line_raw).unwrap_or_default();
476        let column = ColumnNumber::new(column_raw).unwrap_or_default();
477        Ok(Self { callee_idx, file_idx, line, column })
478    }
479}
480
481// HELPER FUNCTIONS
482// ================================================================================================
483
484fn read_string<R: ByteReader>(source: &mut R) -> Result<Arc<str>, DeserializationError> {
485    let len = source.read_usize()?;
486    let bytes = source.read_slice(len)?;
487    let s = core::str::from_utf8(bytes).map_err(|err| {
488        DeserializationError::InvalidValue(alloc::format!("invalid utf-8 in string: {err}"))
489    })?;
490    Ok(Arc::from(s))
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496
497    struct FixedBudgetReader<'a> {
498        inner: miden_core::serde::SliceReader<'a>,
499        max_bytes: usize,
500    }
501
502    impl<'a> FixedBudgetReader<'a> {
503        fn new(bytes: &'a [u8], max_bytes: usize) -> Self {
504            Self {
505                inner: miden_core::serde::SliceReader::new(bytes),
506                max_bytes,
507            }
508        }
509    }
510
511    impl<'a> ByteReader for FixedBudgetReader<'a> {
512        fn read_u8(&mut self) -> Result<u8, DeserializationError> {
513            self.inner.read_u8()
514        }
515
516        fn peek_u8(&self) -> Result<u8, DeserializationError> {
517            self.inner.peek_u8()
518        }
519
520        fn read_slice(&mut self, len: usize) -> Result<&[u8], DeserializationError> {
521            self.inner.read_slice(len)
522        }
523
524        fn read_array<const N: usize>(&mut self) -> Result<[u8; N], DeserializationError> {
525            self.inner.read_array()
526        }
527
528        fn check_eor(&self, num_bytes: usize) -> Result<(), DeserializationError> {
529            self.inner.check_eor(num_bytes)
530        }
531
532        fn has_more_bytes(&self) -> bool {
533            self.inner.has_more_bytes()
534        }
535
536        fn max_alloc(&self, element_size: usize) -> usize {
537            if element_size == 0 {
538                usize::MAX
539            } else {
540                self.max_bytes.checked_div(element_size).unwrap_or(0)
541            }
542        }
543    }
544
545    fn section_with_strings(version: u8, strings_len: usize) -> alloc::vec::Vec<u8> {
546        let mut bytes = alloc::vec::Vec::new();
547        bytes.write_u8(version);
548        bytes.write_usize(strings_len);
549        for _ in 0..strings_len {
550            "".write_into(&mut bytes);
551        }
552        bytes.write_usize(0);
553        bytes
554    }
555
556    fn function_type_bytes(params_len: usize) -> alloc::vec::Vec<u8> {
557        let mut bytes = alloc::vec::Vec::new();
558        bytes.write_u8(TYPE_TAG_FUNCTION);
559        bytes.write_bool(false);
560        bytes.write_usize(params_len);
561        for _ in 0..params_len {
562            bytes.write_u32(0);
563        }
564        bytes
565    }
566
567    fn roundtrip<T: Serializable + Deserializable + PartialEq + core::fmt::Debug>(value: &T) {
568        let mut bytes = alloc::vec::Vec::new();
569        value.write_into(&mut bytes);
570        let result = T::read_from(&mut miden_core::serde::SliceReader::new(&bytes)).unwrap();
571        assert_eq!(value, &result);
572    }
573
574    #[test]
575    fn test_debug_types_section_roundtrip() {
576        let mut section = DebugTypesSection::new();
577
578        // Add primitive types
579        let i32_type_idx = section.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I32));
580        let felt_type_idx = section.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt));
581
582        // Add a pointer type
583        section.add_type(DebugTypeInfo::Pointer { pointee_type_idx: i32_type_idx });
584
585        // Add an array type
586        section.add_type(DebugTypeInfo::Array {
587            element_type_idx: felt_type_idx,
588            count: Some(4),
589        });
590
591        // Add a struct type
592        let x_idx = section.add_string(Arc::from("x"));
593        let y_idx = section.add_string(Arc::from("y"));
594        let point_idx = section.add_string(Arc::from("Point"));
595        section.add_type(DebugTypeInfo::Struct {
596            name_idx: point_idx,
597            size: 16,
598            fields: alloc::vec![
599                DebugFieldInfo {
600                    name_idx: x_idx,
601                    type_idx: felt_type_idx,
602                    offset: 0,
603                },
604                DebugFieldInfo {
605                    name_idx: y_idx,
606                    type_idx: felt_type_idx,
607                    offset: 8,
608                },
609            ],
610        });
611
612        roundtrip(&section);
613    }
614
615    #[test]
616    fn test_debug_sources_section_roundtrip() {
617        let mut section = DebugSourcesSection::new();
618
619        let path_idx = section.add_string(Arc::from("test.rs"));
620        section.add_file(DebugFileInfo::new(path_idx));
621
622        let path2_idx = section.add_string(Arc::from("main.rs"));
623        section.add_file(DebugFileInfo::new(path2_idx).with_checksum([42u8; 32]));
624
625        roundtrip(&section);
626    }
627
628    #[test]
629    fn test_debug_functions_section_roundtrip() {
630        let mut section = DebugFunctionsSection::new();
631
632        let name_idx = section.add_string(Arc::from("test_function"));
633
634        let line = LineNumber::new(10).unwrap();
635        let column = ColumnNumber::new(1).unwrap();
636        let mut func = DebugFunctionInfo::new(name_idx, 0, line, column);
637        let var_name_idx = section.add_string(Arc::from("x"));
638        let var_line = LineNumber::new(10).unwrap();
639        let var_column = ColumnNumber::new(5).unwrap();
640        func.add_variable(
641            DebugVariableInfo::new(var_name_idx, DebugTypeIdx::from(0), var_line, var_column)
642                .with_arg_index(1),
643        );
644        section.add_function(func);
645
646        roundtrip(&section);
647    }
648
649    #[test]
650    fn test_empty_sections_roundtrip() {
651        roundtrip(&DebugTypesSection::new());
652        roundtrip(&DebugSourcesSection::new());
653        roundtrip(&DebugFunctionsSection::new());
654    }
655
656    #[test]
657    fn test_all_primitive_types_roundtrip() {
658        let mut section = DebugTypesSection::new();
659
660        for prim in [
661            DebugPrimitiveType::Void,
662            DebugPrimitiveType::Bool,
663            DebugPrimitiveType::I8,
664            DebugPrimitiveType::U8,
665            DebugPrimitiveType::I16,
666            DebugPrimitiveType::U16,
667            DebugPrimitiveType::I32,
668            DebugPrimitiveType::U32,
669            DebugPrimitiveType::I64,
670            DebugPrimitiveType::U64,
671            DebugPrimitiveType::I128,
672            DebugPrimitiveType::U128,
673            DebugPrimitiveType::F32,
674            DebugPrimitiveType::F64,
675            DebugPrimitiveType::Felt,
676            DebugPrimitiveType::Word,
677        ] {
678            section.add_type(DebugTypeInfo::Primitive(prim));
679        }
680
681        roundtrip(&section);
682    }
683
684    #[test]
685    fn test_function_type_roundtrip() {
686        let ty = DebugTypeInfo::Function {
687            return_type_idx: Some(DebugTypeIdx::from(0)),
688            param_type_indices: alloc::vec![
689                DebugTypeIdx::from(1),
690                DebugTypeIdx::from(2),
691                DebugTypeIdx::from(3)
692            ],
693        };
694        roundtrip(&ty);
695
696        let void_fn = DebugTypeInfo::Function {
697            return_type_idx: None,
698            param_type_indices: alloc::vec![],
699        };
700        roundtrip(&void_fn);
701    }
702
703    #[test]
704    fn test_file_info_with_checksum_roundtrip() {
705        let file = DebugFileInfo::new(0).with_checksum([42u8; 32]);
706        roundtrip(&file);
707    }
708
709    #[test]
710    fn test_function_with_mast_root_roundtrip() {
711        let line1 = LineNumber::new(1).unwrap();
712        let col1 = ColumnNumber::new(1).unwrap();
713        let mut func = DebugFunctionInfo::new(0, 0, line1, col1)
714            .with_linkage_name(1)
715            .with_type(DebugTypeIdx::from(2))
716            .with_mast_root(Word::default());
717
718        let var_line = LineNumber::new(5).unwrap();
719        let var_col = ColumnNumber::new(10).unwrap();
720        func.add_variable(
721            DebugVariableInfo::new(0, DebugTypeIdx::from(0), var_line, var_col)
722                .with_arg_index(1)
723                .with_scope_depth(2),
724        );
725
726        let call_line = LineNumber::new(20).unwrap();
727        let call_col = ColumnNumber::new(5).unwrap();
728        func.add_inlined_call(DebugInlinedCallInfo::new(0, 0, call_line, call_col));
729
730        roundtrip(&func);
731    }
732
733    #[test]
734    fn test_debug_section_string_bounds() {
735        let types_bytes = section_with_strings(DEBUG_TYPES_VERSION, 2);
736        let sources_bytes = section_with_strings(DEBUG_SOURCES_VERSION, 2);
737        let functions_bytes = section_with_strings(DEBUG_FUNCTIONS_VERSION, 2);
738
739        let mut reader = FixedBudgetReader::new(&types_bytes, 1);
740        let err = DebugTypesSection::read_from(&mut reader).unwrap_err();
741        let DeserializationError::InvalidValue(message) = err else {
742            panic!("expected InvalidValue error");
743        };
744        assert!(message.contains("exceeds budget"));
745
746        let mut reader = FixedBudgetReader::new(&sources_bytes, 1);
747        let err = DebugSourcesSection::read_from(&mut reader).unwrap_err();
748        let DeserializationError::InvalidValue(message) = err else {
749            panic!("expected InvalidValue error");
750        };
751        assert!(message.contains("exceeds budget"));
752
753        let mut reader = FixedBudgetReader::new(&functions_bytes, 1);
754        let err = DebugFunctionsSection::read_from(&mut reader).unwrap_err();
755        let DeserializationError::InvalidValue(message) = err else {
756            panic!("expected InvalidValue error");
757        };
758        assert!(message.contains("exceeds budget"));
759
760        let types_ok = section_with_strings(DEBUG_TYPES_VERSION, 1);
761        let sources_ok = section_with_strings(DEBUG_SOURCES_VERSION, 1);
762        let functions_ok = section_with_strings(DEBUG_FUNCTIONS_VERSION, 1);
763
764        let mut reader = FixedBudgetReader::new(&types_ok, 1);
765        assert_eq!(DebugTypesSection::read_from(&mut reader).unwrap().strings.len(), 1);
766
767        let mut reader = FixedBudgetReader::new(&sources_ok, 1);
768        assert_eq!(DebugSourcesSection::read_from(&mut reader).unwrap().strings.len(), 1);
769
770        let mut reader = FixedBudgetReader::new(&functions_ok, 1);
771        assert_eq!(DebugFunctionsSection::read_from(&mut reader).unwrap().strings.len(), 1);
772    }
773
774    #[test]
775    fn test_function_params_bounds() {
776        let too_many = function_type_bytes(2);
777        let mut reader = FixedBudgetReader::new(&too_many, 4);
778        let err = DebugTypeInfo::read_from(&mut reader).unwrap_err();
779        assert!(matches!(err, DeserializationError::InvalidValue(_)));
780
781        let ok = function_type_bytes(1);
782        let mut reader = FixedBudgetReader::new(&ok, 4);
783        let ty = DebugTypeInfo::read_from(&mut reader).unwrap();
784        match ty {
785            DebugTypeInfo::Function { param_type_indices, .. } => {
786                assert_eq!(param_type_indices.len(), 1);
787            },
788            _ => panic!("expected function type"),
789        }
790    }
791}