Skip to main content

miden_mast_package/debug_info/
serialization.rs

1//! Serialization and deserialization for the debug_info section.
2
3use alloc::{string::String, sync::Arc, vec::Vec};
4
5use miden_core::{
6    Word,
7    mast::MastNodeId,
8    serde::{
9        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
10        read_bounded_len,
11    },
12};
13use miden_debug_types::{ByteIndex, ColumnNumber, LineNumber, Location, Uri};
14
15use super::{
16    DEBUG_ERROR_MESSAGES_VERSION, DEBUG_FUNCTIONS_VERSION, DEBUG_SOURCE_GRAPH_VERSION,
17    DEBUG_SOURCE_MAP_VERSION, DEBUG_SOURCES_VERSION, DEBUG_TYPES_VERSION, DebugErrorMessage,
18    DebugErrorMessagesSection, DebugFieldInfo, DebugFileInfo, DebugFunctionInfo,
19    DebugFunctionsSection, DebugPrimitiveType, DebugSourceAsmOp, DebugSourceGraphSection,
20    DebugSourceInlineCall, DebugSourceMapSection, DebugSourceNode, DebugSourceNodeId,
21    DebugSourceVar, DebugSourcesSection, DebugTypeIdx, DebugTypeInfo, DebugTypesSection,
22    DebugVariantInfo, PackageDebugInfo,
23};
24
25// PACKAGE DEBUG INFO SERIALIZATION
26// ================================================================================================
27
28impl Serializable for PackageDebugInfo {
29    fn write_into<W: ByteWriter>(&self, target: &mut W) {
30        target.write_bool(self.types.is_some());
31        if let Some(types) = self.types.as_ref() {
32            types.write_into(target);
33        }
34        target.write_bool(self.sources.is_some());
35        if let Some(sources) = self.sources.as_ref() {
36            sources.write_into(target);
37        }
38        target.write_bool(self.functions.is_some());
39        if let Some(functions) = self.functions.as_ref() {
40            functions.write_into(target);
41        }
42        target.write_bool(self.source_graph.is_some());
43        if let Some(source_graph) = self.source_graph.as_ref() {
44            source_graph.write_into(target);
45        }
46        target.write_bool(self.source_map.is_some());
47        if let Some(source_map) = self.source_map.as_ref() {
48            source_map.write_into(target);
49        }
50        target.write_bool(self.error_messages.is_some());
51        if let Some(error_messages) = self.error_messages.as_ref() {
52            error_messages.write_into(target);
53        }
54    }
55}
56
57impl Deserializable for PackageDebugInfo {
58    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
59        let types = if source.read_bool()? {
60            Some(DebugTypesSection::read_from(source)?)
61        } else {
62            None
63        };
64        let sources = if source.read_bool()? {
65            Some(DebugSourcesSection::read_from(source)?)
66        } else {
67            None
68        };
69        let functions = if source.read_bool()? {
70            Some(DebugFunctionsSection::read_from(source)?)
71        } else {
72            None
73        };
74        let source_graph = if source.read_bool()? {
75            Some(DebugSourceGraphSection::read_from(source)?)
76        } else {
77            None
78        };
79        let source_map = if source.read_bool()? {
80            Some(DebugSourceMapSection::read_from(source)?)
81        } else {
82            None
83        };
84        let error_messages = if source.read_bool()? {
85            Some(DebugErrorMessagesSection::read_from(source)?)
86        } else {
87            None
88        };
89        Ok(Self {
90            types,
91            sources,
92            functions,
93            source_graph,
94            source_map,
95            error_messages,
96        })
97    }
98}
99
100// DEBUG TYPES SECTION SERIALIZATION
101// ================================================================================================
102
103impl Serializable for DebugTypesSection {
104    fn write_into<W: ByteWriter>(&self, target: &mut W) {
105        target.write_u8(self.version);
106
107        // Write string table
108        target.write_usize(self.strings.len());
109        for s in &self.strings {
110            s.as_ref().write_into(target);
111        }
112
113        // Write type table
114        target.write_usize(self.types.len());
115        for ty in &self.types {
116            ty.write_into(target);
117        }
118    }
119}
120
121impl Deserializable for DebugTypesSection {
122    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
123        let version = source.read_u8()?;
124        if version != DEBUG_TYPES_VERSION {
125            return Err(DeserializationError::InvalidValue(alloc::format!(
126                "unsupported debug_types version: {version}, expected {DEBUG_TYPES_VERSION}"
127            )));
128        }
129
130        // Manual bounds check required: read_string is a local helper, not Deserializable,
131        // so we can't use read_many_iter. Each string serializes to at least 1 byte (the
132        // varint length prefix), so max_alloc(1) bounds the vector pre-allocation.
133        let strings_len = read_bounded_len(source, "debug_types strings", 1)?;
134        let mut strings = Vec::with_capacity(strings_len);
135        for _ in 0..strings_len {
136            strings.push(read_string(source)?);
137        }
138
139        let types_len = read_bounded_len(source, "debug_types types", 1)?;
140        let types = source.read_many_iter(types_len)?.collect::<Result<_, _>>()?;
141
142        Ok(Self { version, strings, types })
143    }
144}
145
146// DEBUG SOURCES SECTION SERIALIZATION
147// ================================================================================================
148
149impl Serializable for DebugSourcesSection {
150    fn write_into<W: ByteWriter>(&self, target: &mut W) {
151        target.write_u8(self.version);
152
153        // Write string table
154        target.write_usize(self.strings.len());
155        for s in &self.strings {
156            s.as_ref().write_into(target);
157        }
158
159        // Write file table
160        target.write_usize(self.files.len());
161        for file in &self.files {
162            file.write_into(target);
163        }
164    }
165}
166
167impl Deserializable for DebugSourcesSection {
168    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
169        let version = source.read_u8()?;
170        if version != DEBUG_SOURCES_VERSION {
171            return Err(DeserializationError::InvalidValue(alloc::format!(
172                "unsupported debug_sources version: {version}, expected {DEBUG_SOURCES_VERSION}"
173            )));
174        }
175
176        // Manual bounds check required: read_string is a local helper, not Deserializable,
177        // so we can't use read_many_iter. Each string serializes to at least 1 byte (the
178        // varint length prefix), so max_alloc(1) bounds the vector pre-allocation.
179        let strings_len = read_bounded_len(source, "debug_sources strings", 1)?;
180        let mut strings = Vec::with_capacity(strings_len);
181        for _ in 0..strings_len {
182            strings.push(read_string(source)?);
183        }
184
185        let files_len = read_bounded_len(source, "debug_sources files", 1)?;
186        let files = source.read_many_iter(files_len)?.collect::<Result<_, _>>()?;
187
188        Ok(Self { version, strings, files })
189    }
190}
191
192// DEBUG FUNCTIONS SECTION SERIALIZATION
193// ================================================================================================
194
195impl Serializable for DebugFunctionsSection {
196    fn write_into<W: ByteWriter>(&self, target: &mut W) {
197        target.write_u8(self.version);
198
199        // Write string table
200        target.write_usize(self.strings.len());
201        for s in &self.strings {
202            s.as_ref().write_into(target);
203        }
204
205        // Write function table
206        target.write_usize(self.functions.len());
207        for func in &self.functions {
208            func.write_into(target);
209        }
210    }
211}
212
213impl Deserializable for DebugFunctionsSection {
214    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
215        let version = source.read_u8()?;
216        if version != DEBUG_FUNCTIONS_VERSION {
217            return Err(DeserializationError::InvalidValue(alloc::format!(
218                "unsupported debug_functions version: {version}, expected {DEBUG_FUNCTIONS_VERSION}"
219            )));
220        }
221
222        // Manual bounds check required: read_string is a local helper, not Deserializable,
223        // so we can't use read_many_iter. Each string serializes to at least 1 byte (the
224        // varint length prefix), so max_alloc(1) bounds the vector pre-allocation.
225        let strings_len = read_bounded_len(source, "debug_functions strings", 1)?;
226        let mut strings = Vec::with_capacity(strings_len);
227        for _ in 0..strings_len {
228            strings.push(read_string(source)?);
229        }
230
231        let functions_len = read_bounded_len(source, "debug_functions functions", 1)?;
232        let functions = source.read_many_iter(functions_len)?.collect::<Result<_, _>>()?;
233
234        Ok(Self { version, strings, functions })
235    }
236}
237
238// DEBUG SOURCE GRAPH SECTION SERIALIZATION
239// ================================================================================================
240
241impl Serializable for DebugSourceNode {
242    fn write_into<W: ByteWriter>(&self, target: &mut W) {
243        target.write_u32(self.exec_node.into());
244        self.children.write_into(target);
245        target.write_u32(self.op_start);
246        target.write_u32(self.op_end);
247    }
248}
249
250impl Deserializable for DebugSourceNode {
251    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
252        Ok(Self {
253            exec_node: MastNodeId::new_unchecked(source.read_u32()?),
254            children: read_debug_source_node_ids(source, "debug_source_node children")?,
255            op_start: source.read_u32()?,
256            op_end: source.read_u32()?,
257        })
258    }
259
260    fn min_serialized_size() -> usize {
261        12 + Vec::<DebugSourceNodeId>::min_serialized_size()
262    }
263}
264
265impl Serializable for DebugSourceGraphSection {
266    fn write_into<W: ByteWriter>(&self, target: &mut W) {
267        target.write_u8(self.version());
268        self.nodes().write_into(target);
269        self.roots().write_into(target);
270    }
271}
272
273impl Deserializable for DebugSourceGraphSection {
274    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
275        let version = source.read_u8()?;
276        if version != DEBUG_SOURCE_GRAPH_VERSION {
277            return Err(DeserializationError::InvalidValue(alloc::format!(
278                "unsupported debug_source_graph version: {version}, expected {DEBUG_SOURCE_GRAPH_VERSION}"
279            )));
280        }
281
282        let nodes_len = read_bounded_len(source, "debug_source_graph nodes", 1)?;
283        let nodes = source.read_many_iter(nodes_len)?.collect::<Result<_, _>>()?;
284        let roots = read_debug_source_node_ids(source, "debug_source_graph roots")?;
285        Ok(Self::from_parts(nodes, roots))
286    }
287}
288
289// DEBUG SOURCE MAP SECTION SERIALIZATION
290// ================================================================================================
291
292impl Serializable for DebugSourceAsmOp {
293    fn write_into<W: ByteWriter>(&self, target: &mut W) {
294        self.source_node.write_into(target);
295        target.write_u32(self.op_idx);
296        write_location(&self.location, target);
297        self.context_name.write_into(target);
298        self.op.write_into(target);
299        target.write_u8(self.num_cycles);
300    }
301}
302
303impl Deserializable for DebugSourceAsmOp {
304    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
305        let source_node = DebugSourceNodeId::read_from(source)?;
306        let op_idx = source.read_u32()?;
307        let location = read_location(source)?;
308        let context_name = String::read_from(source)?;
309        let op = String::read_from(source)?;
310        let num_cycles = source.read_u8()?;
311        Ok(Self {
312            source_node,
313            op_idx,
314            location,
315            context_name,
316            op,
317            num_cycles,
318        })
319    }
320
321    fn min_serialized_size() -> usize {
322        DebugSourceNodeId::min_serialized_size() + 4 + 1 + 1 + 1 + 1
323    }
324}
325
326impl Serializable for DebugSourceVar {
327    fn write_into<W: ByteWriter>(&self, target: &mut W) {
328        self.source_node.write_into(target);
329        target.write_u32(self.op_idx);
330        self.var.write_into(target);
331    }
332}
333
334impl Deserializable for DebugSourceVar {
335    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
336        Ok(Self {
337            source_node: DebugSourceNodeId::read_from(source)?,
338            op_idx: source.read_u32()?,
339            var: Deserializable::read_from(source)?,
340        })
341    }
342
343    fn min_serialized_size() -> usize {
344        DebugSourceNodeId::min_serialized_size() + 4
345    }
346}
347
348impl Serializable for DebugSourceInlineCall {
349    fn write_into<W: ByteWriter>(&self, target: &mut W) {
350        self.source_node.write_into(target);
351        target.write_u32(self.op_idx);
352        target.write_u32(self.callee_idx);
353        target.write_u32(self.file_idx);
354        target.write_u32(self.line.to_u32());
355        target.write_u32(self.column.to_u32());
356    }
357}
358
359impl Deserializable for DebugSourceInlineCall {
360    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
361        let source_node = DebugSourceNodeId::read_from(source)?;
362        let op_idx = source.read_u32()?;
363        let callee_idx = source.read_u32()?;
364        let file_idx = source.read_u32()?;
365        let line_raw = source.read_u32()?;
366        let column_raw = source.read_u32()?;
367        let line = LineNumber::new(line_raw).ok_or_else(|| {
368            DeserializationError::InvalidValue(alloc::format!(
369                "debug source inline call line {line_raw} is invalid"
370            ))
371        })?;
372        let column = ColumnNumber::new(column_raw).ok_or_else(|| {
373            DeserializationError::InvalidValue(alloc::format!(
374                "debug source inline call column {column_raw} is invalid"
375            ))
376        })?;
377        Ok(Self::new(source_node, op_idx, callee_idx, file_idx, line, column))
378    }
379
380    fn min_serialized_size() -> usize {
381        DebugSourceNodeId::min_serialized_size() + 20
382    }
383}
384
385impl Serializable for DebugSourceMapSection {
386    fn write_into<W: ByteWriter>(&self, target: &mut W) {
387        target.write_u8(self.version());
388        target.write_usize(self.locations().len());
389        for location in self.locations() {
390            write_required_location(location, target);
391        }
392
393        target.write_usize(self.strings().len());
394        for string in self.strings() {
395            string.write_into(target);
396        }
397
398        target.write_usize(self.asm_ops().len());
399        for asm_op in self.asm_ops() {
400            write_source_asm_op(asm_op, self.locations(), self.strings(), target);
401        }
402
403        self.debug_vars().write_into(target);
404        self.inline_calls().write_into(target);
405    }
406}
407
408impl Deserializable for DebugSourceMapSection {
409    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
410        let version = source.read_u8()?;
411        if version != DEBUG_SOURCE_MAP_VERSION {
412            return Err(DeserializationError::InvalidValue(alloc::format!(
413                "unsupported debug_source_map version: {version}, expected {DEBUG_SOURCE_MAP_VERSION}"
414            )));
415        }
416
417        let locations_len = read_bounded_len(
418            source,
419            "debug_source_map locations",
420            MIN_REQUIRED_LOCATION_SERIALIZED_SIZE,
421        )?;
422        let mut locations = Vec::with_capacity(locations_len);
423        for _ in 0..locations_len {
424            locations.push(read_required_location(source)?);
425        }
426
427        let strings_len = read_bounded_len(source, "debug_source_map strings", 1)?;
428        let mut strings = Vec::with_capacity(strings_len);
429        for _ in 0..strings_len {
430            strings.push(read_owned_string(source)?);
431        }
432
433        let asm_ops_len = read_bounded_len(
434            source,
435            "debug_source_map asm ops",
436            min_source_map_asm_op_row_serialized_size(),
437        )?;
438        let mut asm_ops = Vec::with_capacity(asm_ops_len);
439        for _ in 0..asm_ops_len {
440            asm_ops.push(read_source_asm_op(source, &locations, &strings)?);
441        }
442
443        let debug_vars_len = read_bounded_len(source, "debug_source_map debug vars", 1)?;
444        let debug_vars = source.read_many_iter(debug_vars_len)?.collect::<Result<_, _>>()?;
445        let inline_calls_len = read_bounded_len(source, "debug_source_map inline calls", 1)?;
446        let inline_calls = source.read_many_iter(inline_calls_len)?.collect::<Result<_, _>>()?;
447        Ok(Self::from_parts_with_inline_calls(asm_ops, debug_vars, inline_calls))
448    }
449}
450
451fn write_source_asm_op<W: ByteWriter>(
452    asm_op: &DebugSourceAsmOp,
453    locations: &[Location],
454    strings: &[String],
455    target: &mut W,
456) {
457    asm_op.source_node.write_into(target);
458    target.write_u32(asm_op.op_idx);
459    if let Some(location) = asm_op.location.as_ref() {
460        target.write_bool(true);
461        let location_idx = locations
462            .iter()
463            .position(|candidate| candidate == location)
464            .expect("debug source map location table should contain every row location");
465        target.write_u32(location_idx as u32);
466    } else {
467        target.write_bool(false);
468    }
469    write_source_map_string_ref(&asm_op.context_name, strings, target);
470    write_source_map_string_ref(&asm_op.op, strings, target);
471    target.write_u8(asm_op.num_cycles);
472}
473
474fn read_source_asm_op<R: ByteReader>(
475    source: &mut R,
476    locations: &[Location],
477    strings: &[String],
478) -> Result<DebugSourceAsmOp, DeserializationError> {
479    let source_node = DebugSourceNodeId::read_from(source)?;
480    let op_idx = source.read_u32()?;
481    let location = if source.read_bool()? {
482        let location_idx = source.read_u32()? as usize;
483        Some(locations.get(location_idx).cloned().ok_or_else(|| {
484            DeserializationError::InvalidValue(alloc::format!(
485                "debug source asm op location index {location_idx} out of bounds for {} locations",
486                locations.len()
487            ))
488        })?)
489    } else {
490        None
491    };
492    let context_name = read_source_map_string_ref(source, strings)?;
493    let op = read_source_map_string_ref(source, strings)?;
494    let num_cycles = source.read_u8()?;
495    Ok(DebugSourceAsmOp::new(
496        source_node,
497        op_idx,
498        location,
499        context_name,
500        op,
501        num_cycles,
502    ))
503}
504
505fn min_source_map_asm_op_row_serialized_size() -> usize {
506    // The location index is conditional and is omitted for location-less rows.
507    DebugSourceNodeId::min_serialized_size() + 4 + 1 + 4 + 4 + 1
508}
509
510fn write_source_map_string_ref<W: ByteWriter>(string: &String, strings: &[String], target: &mut W) {
511    let string_idx = strings
512        .iter()
513        .position(|candidate| candidate == string)
514        .expect("debug source map string table should contain every row string");
515    target.write_u32(string_idx as u32);
516}
517
518fn read_source_map_string_ref<R: ByteReader>(
519    source: &mut R,
520    strings: &[String],
521) -> Result<String, DeserializationError> {
522    let string_idx = source.read_u32()? as usize;
523    strings.get(string_idx).cloned().ok_or_else(|| {
524        DeserializationError::InvalidValue(alloc::format!(
525            "debug source asm op string index {string_idx} out of bounds for {} strings",
526            strings.len()
527        ))
528    })
529}
530
531// DEBUG ERROR MESSAGES SECTION SERIALIZATION
532// ================================================================================================
533
534impl Serializable for DebugErrorMessage {
535    fn write_into<W: ByteWriter>(&self, target: &mut W) {
536        target.write_u64(self.err_code);
537        self.message.as_ref().write_into(target);
538    }
539}
540
541impl Deserializable for DebugErrorMessage {
542    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
543        Ok(Self {
544            err_code: source.read_u64()?,
545            message: read_string(source)?,
546        })
547    }
548
549    fn min_serialized_size() -> usize {
550        8 + 1
551    }
552}
553
554impl Serializable for DebugErrorMessagesSection {
555    fn write_into<W: ByteWriter>(&self, target: &mut W) {
556        target.write_u8(self.version());
557        self.messages().write_into(target);
558    }
559}
560
561impl Deserializable for DebugErrorMessagesSection {
562    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
563        let version = source.read_u8()?;
564        if version != DEBUG_ERROR_MESSAGES_VERSION {
565            return Err(DeserializationError::InvalidValue(alloc::format!(
566                "unsupported debug_error_messages version: {version}, expected {DEBUG_ERROR_MESSAGES_VERSION}"
567            )));
568        }
569
570        let messages_len = read_bounded_len(source, "debug_error_messages messages", 1)?;
571        let messages = source.read_many_iter(messages_len)?.collect::<Result<_, _>>()?;
572        Ok(Self::from_parts(messages))
573    }
574}
575
576fn write_location<W: ByteWriter>(location: &Option<Location>, target: &mut W) {
577    if let Some(location) = location {
578        target.write_bool(true);
579        write_required_location(location, target);
580    } else {
581        target.write_bool(false);
582    }
583}
584
585fn read_location<R: ByteReader>(source: &mut R) -> Result<Option<Location>, DeserializationError> {
586    if !source.read_bool()? {
587        return Ok(None);
588    }
589
590    let uri = Uri::read_from(source)?;
591    let start = ByteIndex::new(source.read_u32()?);
592    let end = ByteIndex::new(source.read_u32()?);
593    Ok(Some(Location::new(uri, start, end)))
594}
595
596const MIN_REQUIRED_LOCATION_SERIALIZED_SIZE: usize = 9;
597
598fn write_required_location<W: ByteWriter>(location: &Location, target: &mut W) {
599    location.uri.write_into(target);
600    target.write_u32(location.start.to_u32());
601    target.write_u32(location.end.to_u32());
602}
603
604fn read_required_location<R: ByteReader>(source: &mut R) -> Result<Location, DeserializationError> {
605    let uri = Uri::read_from(source)?;
606    let start = ByteIndex::new(source.read_u32()?);
607    let end = ByteIndex::new(source.read_u32()?);
608    Ok(Location::new(uri, start, end))
609}
610
611// DEBUG TYPE INFO SERIALIZATION
612// ================================================================================================
613
614// Type tags for serialization
615const TYPE_TAG_PRIMITIVE: u8 = 0;
616const TYPE_TAG_POINTER: u8 = 1;
617const TYPE_TAG_ARRAY: u8 = 2;
618const TYPE_TAG_STRUCT: u8 = 3;
619const TYPE_TAG_FUNCTION: u8 = 4;
620const TYPE_TAG_UNKNOWN: u8 = 5;
621const TYPE_TAG_ENUM: u8 = 6;
622
623impl Serializable for DebugTypeInfo {
624    fn write_into<W: ByteWriter>(&self, target: &mut W) {
625        match self {
626            Self::Primitive(prim) => {
627                target.write_u8(TYPE_TAG_PRIMITIVE);
628                target.write_u8(*prim as u8);
629            },
630            Self::Pointer { pointee_type_idx } => {
631                target.write_u8(TYPE_TAG_POINTER);
632                target.write_u32(pointee_type_idx.as_u32());
633            },
634            Self::Array { element_type_idx, count } => {
635                target.write_u8(TYPE_TAG_ARRAY);
636                target.write_u32(element_type_idx.as_u32());
637                target.write_bool(count.is_some());
638                if let Some(count) = count {
639                    target.write_u32(*count);
640                }
641            },
642            Self::Struct { name_idx, size, fields } => {
643                target.write_u8(TYPE_TAG_STRUCT);
644                target.write_u32(*name_idx);
645                target.write_u32(*size);
646                target.write_usize(fields.len());
647                for field in fields {
648                    field.write_into(target);
649                }
650            },
651            Self::Function { return_type_idx, param_type_indices } => {
652                target.write_u8(TYPE_TAG_FUNCTION);
653                target.write_bool(return_type_idx.is_some());
654                if let Some(idx) = return_type_idx {
655                    target.write_u32(idx.as_u32());
656                }
657                target.write_usize(param_type_indices.len());
658                for idx in param_type_indices {
659                    target.write_u32(idx.as_u32());
660                }
661            },
662            Self::Enum {
663                name_idx,
664                size,
665                discriminant_type_idx,
666                variants,
667            } => {
668                target.write_u8(TYPE_TAG_ENUM);
669                target.write_u32(*name_idx);
670                target.write_u32(*size);
671                target.write_u32(discriminant_type_idx.as_u32());
672                target.write_usize(variants.len());
673                for variant in variants {
674                    variant.write_into(target);
675                }
676            },
677            Self::Unknown => {
678                target.write_u8(TYPE_TAG_UNKNOWN);
679            },
680        }
681    }
682}
683
684impl Deserializable for DebugTypeInfo {
685    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
686        let tag = source.read_u8()?;
687        match tag {
688            TYPE_TAG_PRIMITIVE => {
689                let prim_tag = source.read_u8()?;
690                let prim = DebugPrimitiveType::from_discriminant(prim_tag).ok_or_else(|| {
691                    DeserializationError::InvalidValue(alloc::format!(
692                        "invalid primitive type tag: {prim_tag}"
693                    ))
694                })?;
695                Ok(Self::Primitive(prim))
696            },
697            TYPE_TAG_POINTER => {
698                let pointee_type_idx = DebugTypeIdx::from(source.read_u32()?);
699                Ok(Self::Pointer { pointee_type_idx })
700            },
701            TYPE_TAG_ARRAY => {
702                let element_type_idx = DebugTypeIdx::from(source.read_u32()?);
703                let has_count = source.read_bool()?;
704                let count = if has_count { Some(source.read_u32()?) } else { None };
705                Ok(Self::Array { element_type_idx, count })
706            },
707            TYPE_TAG_STRUCT => {
708                let name_idx = source.read_u32()?;
709                let size = source.read_u32()?;
710                let fields_len = read_bounded_len(source, "debug struct fields", 1)?;
711                let fields = source.read_many_iter(fields_len)?.collect::<Result<_, _>>()?;
712                Ok(Self::Struct { name_idx, size, fields })
713            },
714            TYPE_TAG_FUNCTION => {
715                let has_return = source.read_bool()?;
716                let return_type_idx = if has_return {
717                    Some(DebugTypeIdx::from(source.read_u32()?))
718                } else {
719                    None
720                };
721                let param_type_indices =
722                    read_debug_type_indices(source, "debug function parameters")?;
723                Ok(Self::Function { return_type_idx, param_type_indices })
724            },
725            TYPE_TAG_ENUM => {
726                let name_idx = source.read_u32()?;
727                let size = source.read_u32()?;
728                let discriminant_type_idx = DebugTypeIdx::from(source.read_u32()?);
729                let variants_len = read_bounded_len(source, "debug enum variants", 1)?;
730                let variants = source.read_many_iter(variants_len)?.collect::<Result<_, _>>()?;
731                Ok(Self::Enum {
732                    name_idx,
733                    size,
734                    discriminant_type_idx,
735                    variants,
736                })
737            },
738            TYPE_TAG_UNKNOWN => Ok(Self::Unknown),
739            _ => Err(DeserializationError::InvalidValue(alloc::format!("invalid type tag: {tag}"))),
740        }
741    }
742}
743
744// DEBUG FIELD INFO SERIALIZATION
745// ================================================================================================
746
747impl Serializable for DebugFieldInfo {
748    fn write_into<W: ByteWriter>(&self, target: &mut W) {
749        target.write_u32(self.name_idx);
750        target.write_u32(self.type_idx.as_u32());
751        target.write_u32(self.offset);
752    }
753}
754
755impl Deserializable for DebugFieldInfo {
756    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
757        let name_idx = source.read_u32()?;
758        let type_idx = DebugTypeIdx::from(source.read_u32()?);
759        let offset = source.read_u32()?;
760        Ok(Self { name_idx, type_idx, offset })
761    }
762}
763
764// DEBUG VARIANT INFO SERIALIZATION
765// ================================================================================================
766
767impl Serializable for DebugVariantInfo {
768    fn write_into<W: ByteWriter>(&self, target: &mut W) {
769        target.write_u32(self.name_idx);
770        target.write_bool(self.type_idx.is_some());
771        if let Some(type_idx) = self.type_idx {
772            target.write_u32(type_idx.as_u32());
773        }
774        target.write_bool(self.payload_offset.is_some());
775        if let Some(payload_offset) = self.payload_offset {
776            target.write_u32(payload_offset);
777        }
778        target.write_u64((self.discriminant >> 64) as u64);
779        target.write_u64(self.discriminant as u64);
780    }
781}
782
783impl Deserializable for DebugVariantInfo {
784    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
785        let name_idx = source.read_u32()?;
786        let type_idx = if source.read_bool()? {
787            Some(DebugTypeIdx::from(source.read_u32()?))
788        } else {
789            None
790        };
791        let payload_offset = if source.read_bool()? {
792            Some(source.read_u32()?)
793        } else {
794            None
795        };
796        let hi = source.read_u64()? as u128;
797        let lo = source.read_u64()? as u128;
798        Ok(Self {
799            name_idx,
800            type_idx,
801            payload_offset,
802            discriminant: (hi << 64) | lo,
803        })
804    }
805}
806
807// DEBUG FILE INFO SERIALIZATION
808// ================================================================================================
809
810impl Serializable for DebugFileInfo {
811    fn write_into<W: ByteWriter>(&self, target: &mut W) {
812        target.write_u32(self.path_idx);
813
814        target.write_bool(self.checksum.is_some());
815        if let Some(checksum) = &self.checksum {
816            target.write_bytes(checksum.as_ref());
817        }
818    }
819}
820
821impl Deserializable for DebugFileInfo {
822    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
823        let path_idx = source.read_u32()?;
824
825        let has_checksum = source.read_bool()?;
826        let checksum = if has_checksum {
827            let bytes = source.read_slice(32)?;
828            let mut arr = [0u8; 32];
829            arr.copy_from_slice(bytes);
830            Some(alloc::boxed::Box::new(arr))
831        } else {
832            None
833        };
834
835        Ok(Self { path_idx, checksum })
836    }
837}
838
839// DEBUG FUNCTION INFO SERIALIZATION
840// ================================================================================================
841
842impl Serializable for DebugFunctionInfo {
843    fn write_into<W: ByteWriter>(&self, target: &mut W) {
844        target.write_u32(self.name_idx);
845
846        target.write_bool(self.linkage_name_idx.is_some());
847        if let Some(idx) = self.linkage_name_idx {
848            target.write_u32(idx);
849        }
850
851        target.write_u32(self.file_idx);
852        target.write_u32(self.line.to_u32());
853        target.write_u32(self.column.to_u32());
854
855        target.write_bool(self.type_idx.is_some());
856        if let Some(idx) = self.type_idx {
857            target.write_u32(idx.as_u32());
858        }
859
860        target.write_bool(self.mast_root.is_some());
861        if let Some(root) = &self.mast_root {
862            root.write_into(target);
863        }
864    }
865}
866
867impl Deserializable for DebugFunctionInfo {
868    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
869        let name_idx = source.read_u32()?;
870
871        let has_linkage_name = source.read_bool()?;
872        let linkage_name_idx = if has_linkage_name {
873            Some(source.read_u32()?)
874        } else {
875            None
876        };
877
878        let file_idx = source.read_u32()?;
879        let line_raw = source.read_u32()?;
880        let column_raw = source.read_u32()?;
881        let line = LineNumber::new(line_raw).unwrap_or_default();
882        let column = ColumnNumber::new(column_raw).unwrap_or_default();
883
884        let has_type = source.read_bool()?;
885        let type_idx = if has_type {
886            Some(DebugTypeIdx::from(source.read_u32()?))
887        } else {
888            None
889        };
890
891        let has_mast_root = source.read_bool()?;
892        let mast_root = if has_mast_root {
893            Some(Word::read_from(source)?)
894        } else {
895            None
896        };
897
898        Ok(Self {
899            name_idx,
900            linkage_name_idx,
901            file_idx,
902            line,
903            column,
904            type_idx,
905            mast_root,
906        })
907    }
908}
909
910// HELPER FUNCTIONS
911// ================================================================================================
912
913fn read_string<R: ByteReader>(source: &mut R) -> Result<Arc<str>, DeserializationError> {
914    let len = read_bounded_len(source, "debug string bytes", 1)?;
915    let bytes = source.read_slice(len)?;
916    let s = core::str::from_utf8(bytes).map_err(|err| {
917        DeserializationError::InvalidValue(alloc::format!("invalid utf-8 in string: {err}"))
918    })?;
919    Ok(Arc::from(s))
920}
921
922fn read_owned_string<R: ByteReader>(source: &mut R) -> Result<String, DeserializationError> {
923    read_string(source).map(|value| String::from(value.as_ref()))
924}
925
926fn read_debug_source_node_ids<R: ByteReader>(
927    source: &mut R,
928    label: &str,
929) -> Result<Vec<DebugSourceNodeId>, DeserializationError> {
930    let len = read_bounded_len(source, label, DebugSourceNodeId::min_serialized_size())?;
931    source.read_many_iter(len)?.collect::<Result<_, _>>()
932}
933
934fn read_debug_type_indices<R: ByteReader>(
935    source: &mut R,
936    label: &str,
937) -> Result<Vec<DebugTypeIdx>, DeserializationError> {
938    let len = read_bounded_len(source, label, DebugTypeIdx::min_serialized_size())?;
939    source.read_many_iter(len)?.collect::<Result<_, _>>()
940}
941
942#[cfg(test)]
943mod tests {
944    use miden_core::operations::{DebugVarInfo, DebugVarLocation};
945
946    use super::*;
947
948    struct FixedBudgetReader<'a> {
949        inner: miden_core::serde::SliceReader<'a>,
950        max_bytes: usize,
951    }
952
953    impl<'a> FixedBudgetReader<'a> {
954        fn new(bytes: &'a [u8], max_bytes: usize) -> Self {
955            Self {
956                inner: miden_core::serde::SliceReader::new(bytes),
957                max_bytes,
958            }
959        }
960    }
961
962    impl<'a> ByteReader for FixedBudgetReader<'a> {
963        fn read_u8(&mut self) -> Result<u8, DeserializationError> {
964            self.inner.read_u8()
965        }
966
967        fn peek_u8(&self) -> Result<u8, DeserializationError> {
968            self.inner.peek_u8()
969        }
970
971        fn read_slice(&mut self, len: usize) -> Result<&[u8], DeserializationError> {
972            self.inner.read_slice(len)
973        }
974
975        fn read_array<const N: usize>(&mut self) -> Result<[u8; N], DeserializationError> {
976            self.inner.read_array()
977        }
978
979        fn check_eor(&self, num_bytes: usize) -> Result<(), DeserializationError> {
980            self.inner.check_eor(num_bytes)
981        }
982
983        fn has_more_bytes(&self) -> bool {
984            self.inner.has_more_bytes()
985        }
986
987        fn max_alloc(&self, element_size: usize) -> usize {
988            if element_size == 0 {
989                usize::MAX
990            } else {
991                self.max_bytes.checked_div(element_size).unwrap_or(0)
992            }
993        }
994    }
995
996    fn section_with_strings(version: u8, strings_len: usize) -> Vec<u8> {
997        let mut bytes = Vec::new();
998        bytes.write_u8(version);
999        bytes.write_usize(strings_len);
1000        for _ in 0..strings_len {
1001            "".write_into(&mut bytes);
1002        }
1003        bytes.write_usize(0);
1004        bytes
1005    }
1006
1007    fn function_type_bytes(params_len: usize) -> Vec<u8> {
1008        let mut bytes = Vec::new();
1009        bytes.write_u8(TYPE_TAG_FUNCTION);
1010        bytes.write_bool(false);
1011        bytes.write_usize(params_len);
1012        for _ in 0..params_len {
1013            bytes.write_u32(0);
1014        }
1015        bytes
1016    }
1017
1018    fn roundtrip<T: Serializable + Deserializable + PartialEq + core::fmt::Debug>(value: &T) {
1019        let mut bytes = Vec::new();
1020        value.write_into(&mut bytes);
1021        let result = T::read_from(&mut miden_core::serde::SliceReader::new(&bytes)).unwrap();
1022        assert_eq!(value, &result);
1023    }
1024
1025    #[test]
1026    fn test_debug_types_section_roundtrip() {
1027        let mut section = DebugTypesSection::new();
1028
1029        // Add primitive types
1030        let i32_type_idx = section.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I32));
1031        let felt_type_idx = section.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt));
1032
1033        // Add a pointer type
1034        section.add_type(DebugTypeInfo::Pointer { pointee_type_idx: i32_type_idx });
1035
1036        // Add an array type
1037        section.add_type(DebugTypeInfo::Array {
1038            element_type_idx: felt_type_idx,
1039            count: Some(4),
1040        });
1041
1042        // Add a struct type
1043        let x_idx = section.add_string(Arc::from("x"));
1044        let y_idx = section.add_string(Arc::from("y"));
1045        let point_idx = section.add_string(Arc::from("Point"));
1046        section.add_type(DebugTypeInfo::Struct {
1047            name_idx: point_idx,
1048            size: 16,
1049            fields: alloc::vec![
1050                DebugFieldInfo {
1051                    name_idx: x_idx,
1052                    type_idx: felt_type_idx,
1053                    offset: 0,
1054                },
1055                DebugFieldInfo {
1056                    name_idx: y_idx,
1057                    type_idx: felt_type_idx,
1058                    offset: 8,
1059                },
1060            ],
1061        });
1062
1063        // Add an enum type
1064        let status_idx = section.add_string(Arc::from("Status"));
1065        let ok_idx = section.add_string(Arc::from("Ok"));
1066        let err_idx = section.add_string(Arc::from("Err"));
1067        section.add_type(DebugTypeInfo::Enum {
1068            name_idx: status_idx,
1069            size: 8,
1070            discriminant_type_idx: i32_type_idx,
1071            variants: alloc::vec![
1072                DebugVariantInfo {
1073                    name_idx: ok_idx,
1074                    type_idx: None,
1075                    payload_offset: None,
1076                    discriminant: 0,
1077                },
1078                DebugVariantInfo {
1079                    name_idx: err_idx,
1080                    type_idx: Some(felt_type_idx),
1081                    payload_offset: Some(8),
1082                    discriminant: 1,
1083                },
1084            ],
1085        });
1086
1087        roundtrip(&section);
1088    }
1089
1090    #[test]
1091    fn test_debug_sources_section_roundtrip() {
1092        let mut section = DebugSourcesSection::new();
1093
1094        let path_idx = section.add_string(Arc::from("test.rs"));
1095        section.add_file(DebugFileInfo::new(path_idx));
1096
1097        let path2_idx = section.add_string(Arc::from("main.rs"));
1098        section.add_file(DebugFileInfo::new(path2_idx).with_checksum([42u8; 32]));
1099
1100        roundtrip(&section);
1101    }
1102
1103    #[test]
1104    fn test_debug_functions_section_roundtrip() {
1105        let mut section = DebugFunctionsSection::new();
1106
1107        let name_idx = section.add_string(Arc::from("test_function"));
1108
1109        let line = LineNumber::new(10).unwrap();
1110        let column = ColumnNumber::new(1).unwrap();
1111        let func = DebugFunctionInfo::new(name_idx, 0, line, column);
1112        section.add_function(func);
1113
1114        roundtrip(&section);
1115    }
1116
1117    #[test]
1118    fn test_debug_source_graph_section_roundtrip() {
1119        let section = DebugSourceGraphSection::from_parts(
1120            alloc::vec![
1121                DebugSourceNode::new(MastNodeId::new_unchecked(0), alloc::vec![], 0, 1),
1122                DebugSourceNode::new(
1123                    MastNodeId::new_unchecked(1),
1124                    alloc::vec![DebugSourceNodeId::from(0)],
1125                    1,
1126                    3,
1127                ),
1128            ],
1129            alloc::vec![DebugSourceNodeId::from(1)],
1130        );
1131
1132        roundtrip(&section);
1133    }
1134
1135    #[test]
1136    fn test_debug_source_map_section_roundtrip() {
1137        let source_node = DebugSourceNodeId::from(0);
1138        let section = DebugSourceMapSection::from_parts_with_inline_calls(
1139            alloc::vec![DebugSourceAsmOp::new(
1140                source_node,
1141                2,
1142                None,
1143                "test::ctx".into(),
1144                "add".into(),
1145                1,
1146            )],
1147            alloc::vec![DebugSourceVar::new(
1148                source_node,
1149                2,
1150                DebugVarInfo::new("x", DebugVarLocation::Stack(0)),
1151            )],
1152            alloc::vec![DebugSourceInlineCall::new(
1153                source_node,
1154                2,
1155                0,
1156                0,
1157                LineNumber::new(10).unwrap(),
1158                ColumnNumber::new(5).unwrap(),
1159            )],
1160        );
1161
1162        roundtrip(&section);
1163    }
1164
1165    #[test]
1166    fn test_debug_source_map_locationless_asm_op_min_size() {
1167        let source_node = DebugSourceNodeId::from(0);
1168        let asm_op = |op_idx| {
1169            DebugSourceAsmOp::new(source_node, op_idx, None, "test::ctx".into(), "add".into(), 1)
1170        };
1171        let section = DebugSourceMapSection::from_parts(
1172            // Three location-less rows exceed the trailing empty debug_vars/inline_calls
1173            // length prefixes, so an overestimated row size rejects this section.
1174            alloc::vec![asm_op(2), asm_op(3), asm_op(4)],
1175            alloc::vec![],
1176        );
1177
1178        let bytes = section.to_bytes();
1179        let deserialized = DebugSourceMapSection::read_from_bytes(&bytes).unwrap();
1180        assert_eq!(deserialized.asm_ops(), section.asm_ops());
1181    }
1182
1183    #[test]
1184    fn test_debug_source_map_locations_are_deduplicated() {
1185        let source_node = DebugSourceNodeId::from(0);
1186        let location =
1187            Location::new(Uri::new("file://test.masm"), ByteIndex::new(10), ByteIndex::new(14));
1188        let section = DebugSourceMapSection::from_parts(
1189            alloc::vec![
1190                DebugSourceAsmOp::new(
1191                    source_node,
1192                    0,
1193                    Some(location.clone()),
1194                    "test::ctx".into(),
1195                    "push.1".into(),
1196                    1,
1197                ),
1198                DebugSourceAsmOp::new(
1199                    source_node,
1200                    1,
1201                    Some(location.clone()),
1202                    "test::ctx".into(),
1203                    "add".into(),
1204                    1,
1205                ),
1206            ],
1207            alloc::vec![],
1208        );
1209
1210        assert_eq!(section.locations(), &[location]);
1211
1212        let bytes = section.to_bytes();
1213        let deserialized = DebugSourceMapSection::read_from_bytes(&bytes).unwrap();
1214        assert_eq!(deserialized.locations(), section.locations());
1215        assert_eq!(deserialized.asm_ops(), section.asm_ops());
1216    }
1217
1218    #[test]
1219    fn test_debug_source_map_strings_are_deduplicated() {
1220        let source_node = DebugSourceNodeId::from(0);
1221        let section = DebugSourceMapSection::from_parts(
1222            alloc::vec![
1223                DebugSourceAsmOp::new(source_node, 0, None, "test::ctx".into(), "add".into(), 1,),
1224                DebugSourceAsmOp::new(source_node, 1, None, "test::ctx".into(), "mul".into(), 1,),
1225                DebugSourceAsmOp::new(source_node, 2, None, "test::other".into(), "add".into(), 1,),
1226            ],
1227            alloc::vec![],
1228        );
1229
1230        assert_eq!(
1231            section.strings(),
1232            &[
1233                String::from("test::ctx"),
1234                String::from("add"),
1235                String::from("mul"),
1236                String::from("test::other"),
1237            ]
1238        );
1239
1240        let bytes = section.to_bytes();
1241        let deserialized = DebugSourceMapSection::read_from_bytes(&bytes).unwrap();
1242        assert_eq!(deserialized.strings(), section.strings());
1243        assert_eq!(deserialized.asm_ops(), section.asm_ops());
1244    }
1245
1246    #[test]
1247    fn test_debug_error_messages_section_roundtrip() {
1248        let section = DebugErrorMessagesSection::from_parts(alloc::vec![DebugErrorMessage::new(
1249            42,
1250            Arc::from("assertion message"),
1251        )]);
1252
1253        roundtrip(&section);
1254        assert_eq!(section.message(42).as_deref(), Some("assertion message"));
1255    }
1256
1257    #[test]
1258    fn test_empty_sections_roundtrip() {
1259        roundtrip(&DebugTypesSection::new());
1260        roundtrip(&DebugSourcesSection::new());
1261        roundtrip(&DebugFunctionsSection::new());
1262        roundtrip(&DebugSourceGraphSection::new());
1263        roundtrip(&DebugSourceMapSection::new());
1264        roundtrip(&DebugErrorMessagesSection::new());
1265    }
1266
1267    #[test]
1268    fn test_all_primitive_types_roundtrip() {
1269        let mut section = DebugTypesSection::new();
1270
1271        for prim in [
1272            DebugPrimitiveType::Void,
1273            DebugPrimitiveType::Bool,
1274            DebugPrimitiveType::I8,
1275            DebugPrimitiveType::U8,
1276            DebugPrimitiveType::I16,
1277            DebugPrimitiveType::U16,
1278            DebugPrimitiveType::I32,
1279            DebugPrimitiveType::U32,
1280            DebugPrimitiveType::I64,
1281            DebugPrimitiveType::U64,
1282            DebugPrimitiveType::I128,
1283            DebugPrimitiveType::U128,
1284            DebugPrimitiveType::F32,
1285            DebugPrimitiveType::F64,
1286            DebugPrimitiveType::Felt,
1287            DebugPrimitiveType::Word,
1288            DebugPrimitiveType::U256,
1289        ] {
1290            section.add_type(DebugTypeInfo::Primitive(prim));
1291        }
1292
1293        roundtrip(&section);
1294    }
1295
1296    #[test]
1297    fn test_function_type_roundtrip() {
1298        let ty = DebugTypeInfo::Function {
1299            return_type_idx: Some(DebugTypeIdx::from(0)),
1300            param_type_indices: alloc::vec![
1301                DebugTypeIdx::from(1),
1302                DebugTypeIdx::from(2),
1303                DebugTypeIdx::from(3)
1304            ],
1305        };
1306        roundtrip(&ty);
1307
1308        let void_fn = DebugTypeInfo::Function {
1309            return_type_idx: None,
1310            param_type_indices: alloc::vec![],
1311        };
1312        roundtrip(&void_fn);
1313    }
1314
1315    #[test]
1316    fn test_file_info_with_checksum_roundtrip() {
1317        let file = DebugFileInfo::new(0).with_checksum([42u8; 32]);
1318        roundtrip(&file);
1319    }
1320
1321    #[test]
1322    fn test_function_with_mast_root_roundtrip() {
1323        let line1 = LineNumber::new(1).unwrap();
1324        let col1 = ColumnNumber::new(1).unwrap();
1325        let func = DebugFunctionInfo::new(0, 0, line1, col1)
1326            .with_linkage_name(1)
1327            .with_type(DebugTypeIdx::from(2))
1328            .with_mast_root(Word::default());
1329
1330        roundtrip(&func);
1331    }
1332
1333    #[test]
1334    fn test_debug_functions_v1_is_rejected() {
1335        let bytes = section_with_strings(1, 0);
1336        let mut reader = miden_core::serde::SliceReader::new(&bytes);
1337        let err = DebugFunctionsSection::read_from(&mut reader).unwrap_err();
1338        let DeserializationError::InvalidValue(message) = err else {
1339            panic!("expected InvalidValue error");
1340        };
1341        assert!(message.contains("unsupported debug_functions version: 1"));
1342    }
1343
1344    #[test]
1345    fn test_debug_section_string_bounds() {
1346        let types_bytes = section_with_strings(DEBUG_TYPES_VERSION, 2);
1347        let sources_bytes = section_with_strings(DEBUG_SOURCES_VERSION, 2);
1348        let functions_bytes = section_with_strings(DEBUG_FUNCTIONS_VERSION, 2);
1349
1350        let mut reader = FixedBudgetReader::new(&types_bytes, 1);
1351        let err = DebugTypesSection::read_from(&mut reader).unwrap_err();
1352        let DeserializationError::InvalidValue(message) = err else {
1353            panic!("expected InvalidValue error");
1354        };
1355        assert!(message.contains("exceeds budget"));
1356
1357        let mut reader = FixedBudgetReader::new(&sources_bytes, 1);
1358        let err = DebugSourcesSection::read_from(&mut reader).unwrap_err();
1359        let DeserializationError::InvalidValue(message) = err else {
1360            panic!("expected InvalidValue error");
1361        };
1362        assert!(message.contains("exceeds budget"));
1363
1364        let mut reader = FixedBudgetReader::new(&functions_bytes, 1);
1365        let err = DebugFunctionsSection::read_from(&mut reader).unwrap_err();
1366        let DeserializationError::InvalidValue(message) = err else {
1367            panic!("expected InvalidValue error");
1368        };
1369        assert!(message.contains("exceeds budget"));
1370
1371        let types_ok = section_with_strings(DEBUG_TYPES_VERSION, 1);
1372        let sources_ok = section_with_strings(DEBUG_SOURCES_VERSION, 1);
1373        let functions_ok = section_with_strings(DEBUG_FUNCTIONS_VERSION, 1);
1374
1375        let mut reader = FixedBudgetReader::new(&types_ok, 1);
1376        assert_eq!(DebugTypesSection::read_from(&mut reader).unwrap().strings.len(), 1);
1377
1378        let mut reader = FixedBudgetReader::new(&sources_ok, 1);
1379        assert_eq!(DebugSourcesSection::read_from(&mut reader).unwrap().strings.len(), 1);
1380
1381        let mut reader = FixedBudgetReader::new(&functions_ok, 1);
1382        assert_eq!(DebugFunctionsSection::read_from(&mut reader).unwrap().strings.len(), 1);
1383    }
1384
1385    #[test]
1386    fn test_debug_functions_rejects_oversized_string_table_count() {
1387        let bytes = [0x02, 0x08, 0x2a, 0xfe, 0xfe, 0x01];
1388        let mut reader = miden_core::serde::SliceReader::new(&bytes);
1389        let err = DebugFunctionsSection::read_from(&mut reader).unwrap_err();
1390        let DeserializationError::InvalidValue(message) = err else {
1391            panic!("expected InvalidValue error");
1392        };
1393        assert!(message.contains("debug_functions strings count"));
1394        assert!(message.contains("exceeds remaining input"));
1395    }
1396
1397    #[test]
1398    fn test_function_params_bounds() {
1399        let too_many = function_type_bytes(2);
1400        let mut reader = FixedBudgetReader::new(&too_many, 4);
1401        let err = DebugTypeInfo::read_from(&mut reader).unwrap_err();
1402        assert!(matches!(err, DeserializationError::InvalidValue(_)));
1403
1404        let ok = function_type_bytes(1);
1405        let mut reader = FixedBudgetReader::new(&ok, 4);
1406        let ty = DebugTypeInfo::read_from(&mut reader).unwrap();
1407        match ty {
1408            DebugTypeInfo::Function { param_type_indices, .. } => {
1409                assert_eq!(param_type_indices.len(), 1);
1410            },
1411            _ => panic!("expected function type"),
1412        }
1413    }
1414}