Skip to main content

miden_mast_package/debug_info/
serialization.rs

1//! Serialization and deserialization for the debug_info section.
2
3use alloc::{string::ToString, sync::Arc, vec::Vec};
4use core::{alloc::Layout, ptr::NonNull};
5
6use miden_assembly_syntax::ast::DebugVarLocation;
7use miden_core::{
8    Felt, Word,
9    mast::MastNodeId,
10    serde::{
11        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
12        read_bounded_len,
13    },
14};
15use miden_debug_types::{ColumnIndex, LineIndex};
16use miden_utils_indexing::IndexVec;
17use zerocopy::{Immutable, IntoBytes, KnownLayout};
18
19use super::{
20    DEBUG_INFO_VERSION, DebugErrorMessage, DebugFieldInfo, DebugFileIdx, DebugFileInfo,
21    DebugFunctionIdx, DebugFunctionInfo, DebugLoc, DebugLocIdx, DebugPrimitiveType,
22    DebugSourceAsmOp, DebugSourceInlineCall, DebugSourceNode, DebugSourceNodeId, DebugSourceVar,
23    DebugStringIdx, DebugTypeIdx, DebugTypeInfo, DebugVariantInfo, MAX_DEBUG_INFO_PAYLOAD_SIZE,
24    MAX_DEBUG_INFO_STRING_ROWS, MAX_DEBUG_INFO_STRING_SIZE, MAX_DEBUG_INFO_TYPE_ROWS,
25    OptionalIndex, PackageDebugInfo,
26};
27
28/// Base alignment for copied payloads. The assertions below ensure that this is sufficient for
29/// every row type decoded directly from the payload.
30const POD_BUFFER_ALIGNMENT: usize = align_of::<u64>();
31
32const _: () = {
33    assert!(align_of::<DebugFileInfo>() <= POD_BUFFER_ALIGNMENT);
34    assert!(align_of::<DebugLoc>() <= POD_BUFFER_ALIGNMENT);
35    assert!(align_of::<WireDebugFunctionInfo>() <= POD_BUFFER_ALIGNMENT);
36    assert!(align_of::<DebugSourceNodeId>() <= POD_BUFFER_ALIGNMENT);
37    assert!(align_of::<DebugErrorMessage>() <= POD_BUFFER_ALIGNMENT);
38    assert!(align_of::<DebugSourceAsmOp>() <= POD_BUFFER_ALIGNMENT);
39};
40
41/// Wire form of [`DebugFunctionInfo`]. The domain type cannot be decoded from arbitrary bytes
42/// because its field elements must be validated before constructing a [`Word`].
43#[repr(C, align(8))]
44#[derive(Clone, Copy, zerocopy::FromBytes, Immutable, IntoBytes, KnownLayout)]
45struct WireDebugFunctionInfo {
46    mast_root: [u64; 4],
47    source_node: OptionalIndex<DebugSourceNodeId>,
48    type_idx: OptionalIndex<DebugTypeIdx>,
49    linkage_name_idx: OptionalIndex<DebugStringIdx>,
50    name_idx: DebugStringIdx,
51    file_idx: DebugFileIdx,
52    line: LineIndex,
53    column: ColumnIndex,
54}
55
56#[derive(Clone, Copy)]
57struct PackageDebugInfoDecodeLimits {
58    payload_size: usize,
59    string_rows: usize,
60    string_size: usize,
61    type_rows: usize,
62}
63
64impl PackageDebugInfoDecodeLimits {
65    const BOUNDED: Self = Self {
66        payload_size: MAX_DEBUG_INFO_PAYLOAD_SIZE,
67        string_rows: MAX_DEBUG_INFO_STRING_ROWS,
68        string_size: MAX_DEBUG_INFO_STRING_SIZE,
69        type_rows: MAX_DEBUG_INFO_TYPE_ROWS,
70    };
71
72    const UNMETERED: Self = Self {
73        payload_size: usize::MAX,
74        string_rows: usize::MAX,
75        string_size: usize::MAX,
76        type_rows: usize::MAX,
77    };
78}
79
80fn check_decode_limit(
81    resource: &'static str,
82    actual: usize,
83    limit: usize,
84) -> Result<(), super::DebugInfoEncodingError> {
85    if actual > limit {
86        return Err(super::DebugInfoEncodingError::LimitExceeded { resource, size: actual, limit });
87    }
88    Ok(())
89}
90
91// PACKAGE DEBUG INFO SERIALIZATION
92// ================================================================================================
93
94/// Fixed-size tables are padded to their row alignment and written as `zerocopy`-certified rows.
95/// Deserialization copies each payload into an aligned allocation because the padding preserves row
96/// alignment only when measured from an aligned payload base. The function table uses an explicit
97/// wire row because its field elements require validation before constructing domain values.
98#[cfg(target_endian = "little")]
99impl Serializable for PackageDebugInfo {
100    fn write_into<W: ByteWriter>(&self, target: &mut W) {
101        let mut output = Vec::<u8>::with_capacity(16 * 1024);
102
103        self.strings.write_into(&mut output);
104
105        output.write_u32(self.files().len().try_into().unwrap());
106        write_pod_slice(self.files().as_slice(), &mut output);
107
108        output.write_u32(self.locations().len().try_into().unwrap());
109        write_pod_slice(self.locations().as_slice(), &mut output);
110
111        self.types.write_into(&mut output);
112
113        output.write_u32(self.functions().len().try_into().unwrap());
114        pad_to_align::<WireDebugFunctionInfo>(&mut output);
115        write_pod_rows(
116            self.functions().iter().map(|row| {
117                let mast_root = row.mast_root.into_elements().map(|felt| felt.as_canonical_u64());
118                WireDebugFunctionInfo {
119                    mast_root,
120                    source_node: row.source_node,
121                    type_idx: row.type_idx,
122                    linkage_name_idx: row.linkage_name_idx,
123                    name_idx: row.name_idx,
124                    file_idx: row.file_idx,
125                    line: row.line,
126                    column: row.column,
127                }
128            }),
129            &mut output,
130        );
131
132        self.nodes.write_into(&mut output);
133
134        output.write_u32(self.roots().len().try_into().unwrap());
135        write_pod_slice(self.roots(), &mut output);
136
137        output.write_u32(self.error_messages().len().try_into().unwrap());
138        write_pod_slice(self.error_messages(), &mut output);
139
140        target.write_u8(self.version());
141        target.write_usize(output.len());
142        target.write_bytes(&output);
143    }
144}
145
146#[cfg(target_endian = "little")]
147impl PackageDebugInfo {
148    /// Encodes debug information after checking the standard decoder's fixed resource limits.
149    ///
150    /// This lets producers reject metadata that a normal package consumer could not read, without
151    /// decoding another copy of the tables. Ordinary serialization remains unmetered. This checks
152    /// resource limits, not table references or the package's source graph.
153    pub fn to_bytes_with_standard_limits(&self) -> Result<Vec<u8>, super::DebugInfoEncodingError> {
154        self.to_bytes_with_limits(PackageDebugInfoDecodeLimits::BOUNDED)
155    }
156
157    fn to_bytes_with_limits(
158        &self,
159        limits: PackageDebugInfoDecodeLimits,
160    ) -> Result<Vec<u8>, super::DebugInfoEncodingError> {
161        for (label, actual, limit) in [
162            ("debug_info strings count", self.strings.len(), limits.string_rows),
163            ("debug_info types count", self.types.len(), limits.type_rows),
164        ] {
165            check_decode_limit(label, actual, limit)?;
166        }
167        for string in self.strings.iter() {
168            check_decode_limit("debug string size", string.len(), limits.string_size)?;
169        }
170
171        let bytes = self.to_bytes();
172        let mut header = miden_core::serde::SliceReader::new(&bytes);
173        header.read_u8()?;
174        let payload_size = header.read_usize()?;
175        check_decode_limit("package debug info payload size", payload_size, limits.payload_size)?;
176        Ok(bytes)
177    }
178
179    /// Reads package debug information without the fixed resource limits enforced by
180    /// [`Self::read_from`].
181    ///
182    /// Use this for data from a trusted producer or in analysis tooling where the caller accepts
183    /// its memory and processing costs. This method still checks the wire format and honors the
184    /// reader's remaining input and allocation budget. Use [`Self::read_from`] for potentially
185    /// adversarial input with the standard fixed limits.
186    pub fn read_from_unmetered<R: ByteReader>(
187        source: &mut R,
188    ) -> Result<Self, DeserializationError> {
189        Self::read_from_with_limits(source, PackageDebugInfoDecodeLimits::UNMETERED)
190    }
191
192    /// Reads package debug information from `bytes` without the fixed resource limits enforced by
193    /// [`Self::read_from_bytes`].
194    ///
195    /// Use this only when the caller accepts the memory and processing costs of the encoded data.
196    /// The wire-format checks described by [`Self::read_from_unmetered`] still apply. Use
197    /// [`Self::read_from_bytes`] for potentially adversarial input with the standard fixed limits.
198    pub fn read_from_bytes_unmetered(bytes: &[u8]) -> Result<Self, DeserializationError> {
199        Self::read_from_unmetered(&mut miden_core::serde::SliceReader::new(bytes))
200    }
201
202    fn read_from_with_limits<R: ByteReader>(
203        source: &mut R,
204        limits: PackageDebugInfoDecodeLimits,
205    ) -> Result<Self, DeserializationError> {
206        use alloc::string::ToString;
207
208        let version = source.read_u8()?;
209        if version != DEBUG_INFO_VERSION {
210            return Err(DeserializationError::InvalidValue(format!(
211                "unsupported debug_info version: {version}, expected {DEBUG_INFO_VERSION}"
212            )));
213        }
214
215        let data_len = read_bounded_len(source, "package debug info", 1)?;
216        check_decode_limit("package debug info payload size", data_len, limits.payload_size)
217            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
218        let data = source.read_slice(data_len)?;
219        let aligned = AlignedBytes::copy_from_slice(data, POD_BUFFER_ALIGNMENT)?;
220        let mut source = PodSliceReader::new(aligned.as_slice());
221
222        let strings_len = read_bounded_len(&mut source, "debug_info strings", 1)?;
223        check_decode_limit("debug_info strings count", strings_len, limits.string_rows)
224            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
225        let mut strings = Vec::new();
226        for _ in 0..strings_len {
227            strings.push(read_string(&mut source, limits.string_size)?);
228        }
229        let strings = IndexVec::try_from(strings).map_err(|_| {
230            DeserializationError::InvalidValue(
231                "debug_info strings count exceeds the u32 index range".into(),
232            )
233        })?;
234
235        let files_len = source.read_u32()?;
236        let files = source.read_pod_rows::<DebugFileInfo>(files_len as usize, "debug files")?;
237
238        let locations_len = source.read_u32()?;
239        let locations =
240            source.read_pod_rows::<DebugLoc>(locations_len as usize, "debug locations")?;
241
242        let types_len = read_bounded_len(
243            &mut source,
244            "debug_info types",
245            DebugTypeInfo::min_serialized_size(),
246        )?;
247        check_decode_limit("debug_info types count", types_len, limits.type_rows)
248            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
249        let types = source.read_many_iter(types_len)?.collect::<Result<Vec<DebugTypeInfo>, _>>()?;
250        let types = IndexVec::try_from(types).map_err(|_| {
251            DeserializationError::InvalidValue(
252                "debug_info types count exceeds the u32 index range".into(),
253            )
254        })?;
255
256        let functions_len = source.read_u32()?;
257        let functions = source.read_pod_rows_with::<WireDebugFunctionInfo, _, _>(
258            functions_len as usize,
259            "debug functions",
260            |row| {
261                Ok(DebugFunctionInfo {
262                    mast_root: Word::new([
263                        read_wire_felt(row.mast_root[0])?,
264                        read_wire_felt(row.mast_root[1])?,
265                        read_wire_felt(row.mast_root[2])?,
266                        read_wire_felt(row.mast_root[3])?,
267                    ]),
268                    source_node: row.source_node,
269                    type_idx: row.type_idx,
270                    linkage_name_idx: row.linkage_name_idx,
271                    name_idx: row.name_idx,
272                    file_idx: row.file_idx,
273                    line: row.line,
274                    column: row.column,
275                })
276            },
277        )?;
278
279        let nodes = IndexVec::read_from_bounded(&mut source, "debug_info nodes")?;
280
281        let roots_len = source.read_u32()? as usize;
282        let roots = source.read_pod_rows::<DebugSourceNodeId>(roots_len, "debug source roots")?;
283
284        let error_messages_len = source.read_u32()? as usize;
285        let error_messages = source
286            .read_pod_rows::<DebugErrorMessage>(error_messages_len, "debug error messages")?;
287
288        let remaining_len = source.remaining_len();
289        if remaining_len != 0 {
290            return Err(DeserializationError::InvalidValue(format!(
291                "expected {data_len} bytes to have been read, but {remaining_len} remain in the buffer"
292            )));
293        }
294
295        Ok(PackageDebugInfo {
296            version,
297            strings,
298            files: IndexVec::try_from(files).unwrap(),
299            locations: IndexVec::try_from(locations).unwrap(),
300            types,
301            functions: IndexVec::try_from(functions).unwrap(),
302            nodes,
303            roots,
304            error_messages,
305        })
306    }
307}
308
309#[cfg(target_endian = "little")]
310impl Deserializable for PackageDebugInfo {
311    /// Reads package debug information using fixed resource limits for potentially adversarial
312    /// input.
313    ///
314    /// The limits cap the encoded payload, string table, individual strings, and type table. This
315    /// method also performs the normal wire-format checks. Use [`Self::read_from_unmetered`] only
316    /// when the data may exceed those limits and the caller accepts its resource costs.
317    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
318        Self::read_from_with_limits(source, PackageDebugInfoDecodeLimits::BOUNDED)
319    }
320
321    /// Reads package debug information from `bytes` using fixed resource limits for potentially
322    /// adversarial input.
323    ///
324    /// This is the byte-slice counterpart to [`Self::read_from`]. Use
325    /// [`Self::read_from_bytes_unmetered`] only when the data may exceed the standard limits and
326    /// the caller accepts its resource costs.
327    fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
328        Self::read_from(&mut miden_core::serde::SliceReader::new(bytes))
329    }
330}
331
332// DEBUG SOURCE NODE SERIALIZATION
333// ================================================================================================
334
335/// Fixed-size child and assembly-op tables use the same certified row format as
336/// [`PackageDebugInfo`].
337#[cfg(target_endian = "little")]
338impl Serializable for DebugSourceNode {
339    fn write_into<W: ByteWriter>(&self, target: &mut W) {
340        let mut output = Vec::<u8>::with_capacity(
341            size_of::<DebugSourceNode>()
342                + (self.asm_ops.len() * size_of::<DebugSourceAsmOp>())
343                + (self.debug_vars.len() * size_of::<DebugSourceVar>())
344                + (self.inline_calls.len() * size_of::<DebugSourceInlineCall>()),
345        );
346
347        output.write_u32(self.exec_node.into());
348
349        output.write_u32(self.children.len().try_into().unwrap());
350        write_pod_slice(self.children.as_slice(), &mut output);
351
352        output.write_u32(self.op_start);
353        output.write_u32(self.op_end);
354
355        output.write_u32(self.asm_ops.len().try_into().unwrap());
356        write_pod_slice(self.asm_ops.as_slice(), &mut output);
357
358        self.debug_vars.write_into(&mut output);
359        self.inline_calls.write_into(&mut output);
360
361        target.write_usize(output.len());
362        target.write_bytes(&output);
363    }
364}
365
366#[cfg(target_endian = "little")]
367impl Deserializable for DebugSourceNode {
368    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
369        let data_len = read_bounded_len(source, "debug source node", 1)?;
370        let data = source.read_slice(data_len)?;
371        let aligned = AlignedBytes::copy_from_slice(data, POD_BUFFER_ALIGNMENT)?;
372        let mut source = PodSliceReader::new(aligned.as_slice());
373
374        let exec_node = MastNodeId::new_unchecked(source.read_u32()?);
375
376        let children_len = source.read_u32()? as usize;
377        let children =
378            source.read_pod_rows::<DebugSourceNodeId>(children_len, "debug source children")?;
379
380        let op_start = source.read_u32()?;
381        let op_end = source.read_u32()?;
382
383        let asm_ops_len = source.read_u32()? as usize;
384        let asm_ops =
385            source.read_pod_rows::<DebugSourceAsmOp>(asm_ops_len, "debug assembly operations")?;
386        if let Some(rows) = asm_ops.windows(2).find(|rows| rows[0].op_idx >= rows[1].op_idx) {
387            return Err(DeserializationError::InvalidValue(format!(
388                "debug assembly operation indices are not strictly increasing at {} and {}",
389                rows[0].op_idx, rows[1].op_idx,
390            )));
391        }
392
393        let debug_vars = Vec::read_from(&mut source)?;
394        let inline_calls = Vec::read_from(&mut source)?;
395
396        let remaining_len = source.remaining_len();
397        if remaining_len != 0 {
398            return Err(DeserializationError::InvalidValue(format!(
399                "expected {data_len} bytes to have been read, but {remaining_len} remain in the buffer"
400            )));
401        }
402
403        Ok(Self {
404            exec_node,
405            children,
406            op_start,
407            op_end,
408            asm_ops,
409            debug_vars,
410            inline_calls,
411        })
412    }
413
414    fn min_serialized_size() -> usize {
415        1 + DebugSourceNodeId::min_serialized_size()
416            + Vec::<DebugSourceNodeId>::min_serialized_size()
417            + 8
418            + 1
419            + Vec::<DebugSourceVar>::min_serialized_size()
420            + Vec::<DebugSourceInlineCall>::min_serialized_size()
421    }
422}
423
424// DEBUG SOURCE VARIABLE SERIALIZATION
425// ================================================================================================
426
427impl Serializable for DebugSourceVar {
428    fn write_into<W: ByteWriter>(&self, target: &mut W) {
429        target.write_u32(self.op_idx);
430        self.name_idx.write_into(target);
431        self.type_id.write_into(target);
432        target.write_u32(self.arg_idx.map(core::num::NonZeroU32::get).unwrap_or_default());
433        self.location_idx.write_into(target);
434        self.value_location.write_into(target);
435    }
436}
437
438impl Deserializable for DebugSourceVar {
439    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
440        let op_idx = source.read_u32()?;
441        let name_idx = DebugStringIdx::read_from(source)?;
442        let type_id = Option::<DebugTypeIdx>::read_from(source)?;
443        let arg_idx = core::num::NonZeroU32::new(source.read_u32()?);
444        let location_idx = Option::<DebugLocIdx>::read_from(source)?;
445        let value_location = DebugVarLocation::read_from(source)?;
446        Ok(Self {
447            op_idx,
448            name_idx,
449            type_id,
450            arg_idx,
451            location_idx,
452            value_location,
453        })
454    }
455
456    fn min_serialized_size() -> usize {
457        4 + DebugStringIdx::min_serialized_size()
458            + 1
459            + 4
460            + 1
461            + DebugVarLocation::min_serialized_size()
462    }
463}
464
465// DEBUG INLINE CALL SERIALIZATION
466// ================================================================================================
467
468impl Serializable for DebugSourceInlineCall {
469    fn write_into<W: ByteWriter>(&self, target: &mut W) {
470        target.write_u32(self.op_idx);
471        target.write_u32(self.op_end);
472        self.callee_idx.write_into(target);
473        self.loc_idx.write_into(target);
474    }
475}
476
477impl Deserializable for DebugSourceInlineCall {
478    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
479        let op_idx = source.read_u32()?;
480        let op_end = source.read_u32()?;
481        if op_end < op_idx {
482            return Err(DeserializationError::InvalidValue(
483                "debug inline-call range ends before its start".into(),
484            ));
485        }
486        let callee_idx = DebugFunctionIdx::read_from(source)?;
487        let loc_idx = DebugLocIdx::read_from(source)?;
488        Ok(DebugSourceInlineCall { op_idx, op_end, callee_idx, loc_idx })
489    }
490
491    fn min_serialized_size() -> usize {
492        8 + DebugFunctionIdx::min_serialized_size() + DebugLocIdx::min_serialized_size()
493    }
494}
495
496// DEBUG TYPE INFO SERIALIZATION
497// ================================================================================================
498
499// Type tags for serialization
500const TYPE_TAG_PRIMITIVE: u8 = 0;
501const TYPE_TAG_POINTER: u8 = 1;
502const TYPE_TAG_ARRAY: u8 = 2;
503const TYPE_TAG_STRUCT: u8 = 3;
504const TYPE_TAG_FUNCTION: u8 = 4;
505const TYPE_TAG_UNKNOWN: u8 = 5;
506const TYPE_TAG_ENUM: u8 = 6;
507const TYPE_TAG_VARIADIC: u8 = 7;
508
509impl Serializable for DebugTypeInfo {
510    fn write_into<W: ByteWriter>(&self, target: &mut W) {
511        match self {
512            Self::Primitive(prim) => {
513                target.write_u8(TYPE_TAG_PRIMITIVE);
514                target.write_u8(*prim as u8);
515            },
516            Self::Pointer { pointee_type_idx } => {
517                target.write_u8(TYPE_TAG_POINTER);
518                pointee_type_idx.write_into(target);
519            },
520            Self::Array { element_type_idx, count } => {
521                target.write_u8(TYPE_TAG_ARRAY);
522                element_type_idx.write_into(target);
523                target.write_bool(count.is_some());
524                if let Some(count) = count {
525                    target.write_u32(*count);
526                }
527            },
528            Self::Struct { name_idx, size, fields } => {
529                target.write_u8(TYPE_TAG_STRUCT);
530                name_idx.write_into(target);
531                target.write_u32(*size);
532                target.write_usize(fields.len());
533                for field in fields {
534                    field.write_into(target);
535                }
536            },
537            Self::Function { return_type_idx, param_type_indices } => {
538                target.write_u8(TYPE_TAG_FUNCTION);
539                target.write_bool(return_type_idx.is_some());
540                if let Some(idx) = return_type_idx {
541                    idx.write_into(target);
542                }
543                target.write_usize(param_type_indices.len());
544                for idx in param_type_indices {
545                    idx.write_into(target);
546                }
547            },
548            Self::Enum {
549                name_idx,
550                size,
551                discriminant_type_idx,
552                variants,
553            } => {
554                target.write_u8(TYPE_TAG_ENUM);
555                name_idx.write_into(target);
556                target.write_u32(*size);
557                discriminant_type_idx.write_into(target);
558                target.write_usize(variants.len());
559                for variant in variants {
560                    variant.write_into(target);
561                }
562            },
563            Self::Unknown => {
564                target.write_u8(TYPE_TAG_UNKNOWN);
565            },
566            Self::Variadic => {
567                target.write_u8(TYPE_TAG_VARIADIC);
568            },
569        }
570    }
571}
572
573impl Deserializable for DebugTypeInfo {
574    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
575        let tag = source.read_u8()?;
576        match tag {
577            TYPE_TAG_PRIMITIVE => {
578                let prim_tag = source.read_u8()?;
579                let prim = DebugPrimitiveType::from_discriminant(prim_tag).ok_or_else(|| {
580                    DeserializationError::InvalidValue(alloc::format!(
581                        "invalid primitive type tag: {prim_tag}"
582                    ))
583                })?;
584                Ok(Self::Primitive(prim))
585            },
586            TYPE_TAG_POINTER => {
587                let pointee_type_idx = DebugTypeIdx::from(source.read_u32()?);
588                Ok(Self::Pointer { pointee_type_idx })
589            },
590            TYPE_TAG_ARRAY => {
591                let element_type_idx = DebugTypeIdx::from(source.read_u32()?);
592                let has_count = source.read_bool()?;
593                let count = if has_count { Some(source.read_u32()?) } else { None };
594                Ok(Self::Array { element_type_idx, count })
595            },
596            TYPE_TAG_STRUCT => {
597                let name_idx = DebugStringIdx::read_from(source)?;
598                let size = source.read_u32()?;
599                let fields_len = read_bounded_len(source, "debug struct fields", 1)?;
600                let fields = source.read_many_iter(fields_len)?.collect::<Result<_, _>>()?;
601                Ok(Self::Struct { name_idx, size, fields })
602            },
603            TYPE_TAG_FUNCTION => {
604                let has_return = source.read_bool()?;
605                let return_type_idx = if has_return {
606                    Some(DebugTypeIdx::from(source.read_u32()?))
607                } else {
608                    None
609                };
610                let param_type_indices =
611                    read_debug_type_indices(source, "debug function parameters")?;
612                Ok(Self::Function { return_type_idx, param_type_indices })
613            },
614            TYPE_TAG_ENUM => {
615                let name_idx = DebugStringIdx::read_from(source)?;
616                let size = source.read_u32()?;
617                let discriminant_type_idx = DebugTypeIdx::from(source.read_u32()?);
618                let variants_len = read_bounded_len(source, "debug enum variants", 1)?;
619                let variants = source.read_many_iter(variants_len)?.collect::<Result<_, _>>()?;
620                Ok(Self::Enum {
621                    name_idx,
622                    size,
623                    discriminant_type_idx,
624                    variants,
625                })
626            },
627            TYPE_TAG_UNKNOWN => Ok(Self::Unknown),
628            TYPE_TAG_VARIADIC => Ok(Self::Variadic),
629            _ => Err(DeserializationError::InvalidValue(alloc::format!("invalid type tag: {tag}"))),
630        }
631    }
632
633    fn min_serialized_size() -> usize {
634        // The unknown type consists solely of its tag. All other variants are larger.
635        1
636    }
637}
638
639// DEBUG FIELD INFO SERIALIZATION
640// ================================================================================================
641
642impl Serializable for DebugFieldInfo {
643    fn write_into<W: ByteWriter>(&self, target: &mut W) {
644        self.name_idx.write_into(target);
645        self.type_idx.write_into(target);
646        target.write_u32(self.offset);
647    }
648}
649
650impl Deserializable for DebugFieldInfo {
651    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
652        let name_idx = DebugStringIdx::read_from(source)?;
653        let type_idx = DebugTypeIdx::from(source.read_u32()?);
654        let offset = source.read_u32()?;
655        Ok(Self { name_idx, type_idx, offset })
656    }
657}
658
659// DEBUG VARIANT INFO SERIALIZATION
660// ================================================================================================
661
662impl Serializable for DebugVariantInfo {
663    fn write_into<W: ByteWriter>(&self, target: &mut W) {
664        self.name_idx.write_into(target);
665        target.write_bool(self.type_idx.is_some());
666        if let Some(type_idx) = self.type_idx {
667            type_idx.write_into(target);
668        }
669        target.write_bool(self.payload_offset.is_some());
670        if let Some(payload_offset) = self.payload_offset {
671            target.write_u32(payload_offset);
672        }
673        target.write_u64((self.discriminant >> 64) as u64);
674        target.write_u64(self.discriminant as u64);
675    }
676}
677
678impl Deserializable for DebugVariantInfo {
679    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
680        let name_idx = DebugStringIdx::read_from(source)?;
681        let type_idx = if source.read_bool()? {
682            Some(DebugTypeIdx::from(source.read_u32()?))
683        } else {
684            None
685        };
686        let payload_offset = if source.read_bool()? {
687            Some(source.read_u32()?)
688        } else {
689            None
690        };
691        let hi = source.read_u64()? as u128;
692        let lo = source.read_u64()? as u128;
693        Ok(Self {
694            name_idx,
695            type_idx,
696            payload_offset,
697            discriminant: (hi << 64) | lo,
698        })
699    }
700
701    fn min_serialized_size() -> usize {
702        // The minimum encoding has no payload type or offset: one string-table index, two
703        // one-byte option discriminants, and the two halves of the discriminant value.
704        DebugStringIdx::min_serialized_size()
705            + 2 * u8::min_serialized_size()
706            + 2 * u64::min_serialized_size()
707    }
708}
709
710// DEBUG FILE INFO SERIALIZATION
711// ================================================================================================
712
713impl Serializable for DebugFileInfo {
714    fn write_into<W: ByteWriter>(&self, target: &mut W) {
715        self.path_idx.write_into(target);
716        self.checksum.write_into(target);
717    }
718}
719
720impl Deserializable for DebugFileInfo {
721    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
722        let path_idx = DebugStringIdx::read_from(source)?;
723
724        let bytes = source.read_slice(32)?;
725        let mut checksum = [0u8; 32];
726        checksum.copy_from_slice(bytes);
727
728        Ok(Self { path_idx, checksum })
729    }
730
731    fn min_serialized_size() -> usize {
732        DebugStringIdx::min_serialized_size() + size_of::<[u8; 32]>()
733    }
734}
735
736// HELPER FUNCTIONS
737// ================================================================================================
738
739/// Owns a byte allocation with the alignment required by the certified POD row types.
740struct AlignedBytes {
741    ptr: Option<NonNull<u8>>,
742    layout: Layout,
743}
744
745impl AlignedBytes {
746    fn copy_from_slice(source: &[u8], alignment: usize) -> Result<Self, DeserializationError> {
747        let layout = Layout::from_size_align(source.len(), alignment).map_err(|_| {
748            DeserializationError::InvalidValue(format!(
749                "debug info payload size {} is too large",
750                source.len()
751            ))
752        })?;
753        if source.is_empty() {
754            return Ok(Self { ptr: None, layout });
755        }
756
757        // SAFETY: `layout` is non-zero and valid in this branch.
758        let ptr = unsafe { alloc::alloc::alloc(layout) };
759        let Some(ptr) = NonNull::new(ptr) else {
760            alloc::alloc::handle_alloc_error(layout)
761        };
762        // SAFETY: `ptr` owns `source.len()` writable bytes and does not overlap `source`.
763        unsafe {
764            ptr.as_ptr().copy_from_nonoverlapping(source.as_ptr(), source.len());
765        }
766        Ok(Self { ptr: Some(ptr), layout })
767    }
768
769    fn as_slice(&self) -> &[u8] {
770        let Some(ptr) = self.ptr else {
771            return &[];
772        };
773        // SAFETY: `ptr` was allocated with `self.layout`, every byte was initialized by the copy in
774        // `copy_from_slice`, and the allocation remains owned by `self` for the returned borrow.
775        unsafe { core::slice::from_raw_parts(ptr.as_ptr(), self.layout.size()) }
776    }
777}
778
779impl Drop for AlignedBytes {
780    fn drop(&mut self) {
781        if let Some(ptr) = self.ptr {
782            // SAFETY: `ptr` was allocated with this exact layout and has not been freed.
783            unsafe {
784                alloc::alloc::dealloc(ptr.as_ptr(), self.layout);
785            }
786        }
787    }
788}
789
790struct PodSliceReader<'a> {
791    source: &'a [u8],
792    pos: usize,
793}
794
795impl<'a> PodSliceReader<'a> {
796    fn new(source: &'a [u8]) -> Self {
797        Self { source, pos: 0 }
798    }
799
800    fn remaining_len(&self) -> usize {
801        self.source.len() - self.pos
802    }
803
804    fn read_pod_rows<T>(&mut self, len: usize, label: &str) -> Result<Vec<T>, DeserializationError>
805    where
806        T: Copy + zerocopy::FromBytes + Immutable + KnownLayout,
807    {
808        self.skip_alignment_padding::<T>()?;
809        let byte_len = len.checked_mul(size_of::<T>()).ok_or_else(|| {
810            DeserializationError::InvalidValue(alloc::format!(
811                "{label} row count {len} overflows row size {}",
812                size_of::<T>()
813            ))
814        })?;
815        let bytes = self.read_slice(byte_len)?;
816        let rows: &[T] = <[T] as zerocopy::FromBytes>::ref_from_bytes(bytes).map_err(|_| {
817            DeserializationError::InvalidValue(alloc::format!(
818                "{label} bytes do not form aligned POD rows"
819            ))
820        })?;
821        Ok(rows.to_vec())
822    }
823
824    fn read_pod_rows_with<T, U, F>(
825        &mut self,
826        len: usize,
827        label: &str,
828        map: F,
829    ) -> Result<Vec<U>, DeserializationError>
830    where
831        T: Copy + zerocopy::FromBytes + Immutable + KnownLayout,
832        F: FnMut(T) -> Result<U, DeserializationError>,
833    {
834        self.skip_alignment_padding::<T>()?;
835        let byte_len = len.checked_mul(size_of::<T>()).ok_or_else(|| {
836            DeserializationError::InvalidValue(alloc::format!(
837                "{label} row count {len} overflows row size {}",
838                size_of::<T>()
839            ))
840        })?;
841        let bytes = self.read_slice(byte_len)?;
842        let rows: &[T] = <[T] as zerocopy::FromBytes>::ref_from_bytes(bytes).map_err(|_| {
843            DeserializationError::InvalidValue(alloc::format!(
844                "{label} bytes do not form aligned POD rows"
845            ))
846        })?;
847        rows.iter().copied().map(map).collect()
848    }
849
850    fn skip_alignment_padding<T>(&mut self) -> Result<(), DeserializationError> {
851        let padding_required = self.pos.next_multiple_of(align_of::<T>()) - self.pos;
852        self.pos += padding_required;
853        if self.pos > self.source.len() {
854            Err(DeserializationError::UnexpectedEOF)
855        } else {
856            Ok(())
857        }
858    }
859}
860
861impl ByteReader for PodSliceReader<'_> {
862    fn max_alloc(&self, element_size: usize) -> usize {
863        self.remaining_len().checked_div(element_size).unwrap_or(usize::MAX)
864    }
865
866    fn read_u8(&mut self) -> Result<u8, DeserializationError> {
867        self.check_eor(1)?;
868        let result = self.source[self.pos];
869        self.pos += 1;
870        Ok(result)
871    }
872
873    fn peek_u8(&self) -> Result<u8, DeserializationError> {
874        self.check_eor(1)?;
875        Ok(self.source[self.pos])
876    }
877
878    fn read_slice(&mut self, len: usize) -> Result<&[u8], DeserializationError> {
879        self.check_eor(len)?;
880        let result = &self.source[self.pos..self.pos + len];
881        self.pos += len;
882        Ok(result)
883    }
884
885    fn read_array<const N: usize>(&mut self) -> Result<[u8; N], DeserializationError> {
886        self.check_eor(N)?;
887        let mut result = [0_u8; N];
888        result.copy_from_slice(&self.source[self.pos..self.pos + N]);
889        self.pos += N;
890        Ok(result)
891    }
892
893    fn check_eor(&self, num_bytes: usize) -> Result<(), DeserializationError> {
894        self.pos
895            .checked_add(num_bytes)
896            .filter(|end| *end <= self.source.len())
897            .map(|_| ())
898            .ok_or(DeserializationError::UnexpectedEOF)
899    }
900
901    fn has_more_bytes(&self) -> bool {
902        self.remaining_len() != 0
903    }
904}
905
906fn pad_to_align<T>(output: &mut Vec<u8>) {
907    let padding_required = output.len().next_multiple_of(align_of::<T>()) - output.len();
908    output.resize(output.len() + padding_required, 0);
909}
910
911fn write_pod_slice<T: IntoBytes + Immutable>(slice: &[T], target: &mut Vec<u8>) {
912    pad_to_align::<T>(target);
913    target.write_bytes(slice.as_bytes());
914}
915
916fn write_pod_rows<T, I>(rows: I, target: &mut Vec<u8>)
917where
918    T: IntoBytes + Immutable,
919    I: IntoIterator<Item = T>,
920{
921    let rows: Vec<T> = rows.into_iter().collect();
922    target.write_bytes(rows.as_bytes());
923}
924
925fn read_wire_felt(value: u64) -> Result<Felt, DeserializationError> {
926    Felt::new(value).map_err(|err| {
927        DeserializationError::InvalidValue(alloc::format!(
928            "invalid field element in debug function MAST root: {err}"
929        ))
930    })
931}
932
933fn read_string<R: ByteReader>(
934    source: &mut R,
935    max_size: usize,
936) -> Result<Arc<str>, DeserializationError> {
937    let len = read_bounded_len(source, "debug string bytes", 1)?;
938    check_decode_limit("debug string size", len, max_size)
939        .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
940    let bytes = source.read_slice(len)?;
941    let s = core::str::from_utf8(bytes).map_err(|err| {
942        DeserializationError::InvalidValue(alloc::format!("invalid utf-8 in string: {err}"))
943    })?;
944    Ok(Arc::from(s))
945}
946
947fn read_debug_type_indices<R: ByteReader>(
948    source: &mut R,
949    label: &str,
950) -> Result<Vec<DebugTypeIdx>, DeserializationError> {
951    let len = read_bounded_len(source, label, DebugTypeIdx::min_serialized_size())?;
952    source.read_many_iter(len)?.collect::<Result<_, _>>()
953}
954
955#[cfg(test)]
956mod tests {
957    use alloc::string::ToString;
958    use core::{assert_matches, cell::Cell};
959
960    use miden_assembly_syntax::ast::DebugVarLocation;
961    use miden_core::{Felt, Word};
962    use miden_debug_types::{ByteIndex, ColumnNumber, LineNumber, Location, Uri};
963
964    use super::*;
965    use crate::debug_info::{DebugFileIdx, PackageDebugInfoBuilder};
966
967    struct FixedBudgetReader<'a> {
968        inner: miden_core::serde::SliceReader<'a>,
969        max_bytes: usize,
970        largest_requested_element_size: Cell<usize>,
971    }
972
973    impl<'a> FixedBudgetReader<'a> {
974        fn new(bytes: &'a [u8], max_bytes: usize) -> Self {
975            Self {
976                inner: miden_core::serde::SliceReader::new(bytes),
977                max_bytes,
978                largest_requested_element_size: Cell::new(0),
979            }
980        }
981    }
982
983    impl<'a> ByteReader for FixedBudgetReader<'a> {
984        fn read_u8(&mut self) -> Result<u8, DeserializationError> {
985            self.inner.read_u8()
986        }
987
988        fn peek_u8(&self) -> Result<u8, DeserializationError> {
989            self.inner.peek_u8()
990        }
991
992        fn read_slice(&mut self, len: usize) -> Result<&[u8], DeserializationError> {
993            self.inner.read_slice(len)
994        }
995
996        fn read_array<const N: usize>(&mut self) -> Result<[u8; N], DeserializationError> {
997            self.inner.read_array()
998        }
999
1000        fn check_eor(&self, num_bytes: usize) -> Result<(), DeserializationError> {
1001            self.inner.check_eor(num_bytes)
1002        }
1003
1004        fn has_more_bytes(&self) -> bool {
1005            self.inner.has_more_bytes()
1006        }
1007
1008        fn max_alloc(&self, element_size: usize) -> usize {
1009            self.largest_requested_element_size
1010                .set(self.largest_requested_element_size.get().max(element_size));
1011            if element_size == 0 {
1012                usize::MAX
1013            } else {
1014                self.max_bytes.checked_div(element_size).unwrap_or(0)
1015            }
1016        }
1017    }
1018
1019    fn function_type_bytes(params_len: usize) -> Vec<u8> {
1020        let mut bytes = Vec::new();
1021        bytes.write_u8(TYPE_TAG_FUNCTION);
1022        bytes.write_bool(false);
1023        bytes.write_usize(params_len);
1024        for _ in 0..params_len {
1025            bytes.write_u32(0);
1026        }
1027        bytes
1028    }
1029
1030    fn roundtrip<T: Serializable + Deserializable + PartialEq + core::fmt::Debug>(value: &T) {
1031        let mut bytes = Vec::new();
1032        value.write_into(&mut bytes);
1033        let result = T::read_from(&mut miden_core::serde::SliceReader::new(&bytes)).unwrap();
1034        assert_eq!(value, &result);
1035    }
1036
1037    #[test]
1038    fn pod_row_reader_rejects_byte_length_overflow() {
1039        let mut reader = PodSliceReader::new(&[]);
1040        let result = reader.read_pod_rows::<DebugErrorMessage>(usize::MAX, "test error messages");
1041        let error = result.unwrap_err();
1042
1043        let DeserializationError::InvalidValue(message) = error else {
1044            panic!("expected InvalidValue error");
1045        };
1046        assert!(message.contains("overflows row size"));
1047    }
1048
1049    #[test]
1050    fn pod_row_reader_decodes_certified_rows() {
1051        let expected = DebugErrorMessage::new(42, DebugStringIdx::from(7));
1052        let mut aligned = [0_u64; 2];
1053        aligned.as_mut_bytes()[..size_of::<DebugErrorMessage>()]
1054            .copy_from_slice(expected.as_bytes());
1055        let mut reader = PodSliceReader::new(aligned.as_bytes());
1056        let decoded = reader.read_pod_rows::<DebugErrorMessage>(1, "test error messages").unwrap();
1057
1058        assert_eq!(decoded, [expected]);
1059    }
1060
1061    #[test]
1062    fn debug_variant_min_serialized_size_is_accepted_by_slice_reader() {
1063        let variant = DebugVariantInfo {
1064            name_idx: DebugStringIdx::from(0),
1065            type_idx: None,
1066            payload_offset: None,
1067            discriminant: 0,
1068        };
1069        let mut bytes = Vec::new();
1070        variant.write_into(&mut bytes);
1071
1072        assert_eq!(bytes.len(), DebugVariantInfo::min_serialized_size());
1073
1074        let mut reader = miden_core::serde::SliceReader::new(&bytes);
1075        let mut variants = reader.read_many_iter::<DebugVariantInfo>(1).unwrap();
1076        assert_eq!(variants.next().unwrap().unwrap(), variant);
1077        assert!(variants.next().is_none());
1078    }
1079
1080    #[test]
1081    fn debug_type_initial_capacity_is_bounded_by_in_memory_size() {
1082        const PAYLOAD_BYTES: usize = 256;
1083
1084        let mut bytes = Vec::new();
1085        bytes.write_usize(PAYLOAD_BYTES);
1086        bytes.resize(bytes.len() + PAYLOAD_BYTES, u8::MAX);
1087        let mut reader = FixedBudgetReader::new(&bytes, PAYLOAD_BYTES);
1088
1089        let result =
1090            IndexVec::<DebugTypeIdx, DebugTypeInfo>::read_from_bounded(&mut reader, "debug types");
1091
1092        assert!(result.is_err(), "the first invalid type tag should stop decoding");
1093        assert_eq!(
1094            reader.largest_requested_element_size.get(),
1095            size_of::<DebugTypeInfo>(),
1096            "the speculative capacity must be bounded using the in-memory row size",
1097        );
1098    }
1099
1100    #[test]
1101    fn package_debug_info_trailing_data_error_reports_remaining_bytes() {
1102        const TRAILING: &[u8] = &[0xaa, 0xbb, 0xcc];
1103
1104        let serialized = PackageDebugInfo::default().to_bytes();
1105        let mut reader = miden_core::serde::SliceReader::new(&serialized);
1106        let version = reader.read_u8().unwrap();
1107        let data_len = reader.read_usize().unwrap();
1108        let data = reader.read_slice(data_len).unwrap().to_vec();
1109        assert!(!reader.has_more_bytes());
1110
1111        let mut malformed = Vec::new();
1112        malformed.write_u8(version);
1113        malformed.write_usize(data_len + TRAILING.len());
1114        malformed.write_bytes(&data);
1115        malformed.write_bytes(TRAILING);
1116
1117        let error =
1118            PackageDebugInfo::read_from(&mut miden_core::serde::SliceReader::new(&malformed))
1119                .unwrap_err();
1120        let DeserializationError::InvalidValue(message) = error else {
1121            panic!("expected InvalidValue error");
1122        };
1123        assert!(message.contains("but 3 remain in the buffer"), "{message}");
1124    }
1125
1126    #[test]
1127    fn debug_source_node_trailing_data_error_reports_remaining_bytes() {
1128        const TRAILING: &[u8] = &[0xaa, 0xbb];
1129
1130        let source_node = DebugSourceNode {
1131            exec_node: MastNodeId::new_unchecked(0),
1132            children: Vec::new(),
1133            op_start: 0,
1134            op_end: 0,
1135            asm_ops: Vec::new(),
1136            debug_vars: Vec::new(),
1137            inline_calls: Vec::new(),
1138        };
1139        let serialized = source_node.to_bytes();
1140        let mut reader = miden_core::serde::SliceReader::new(&serialized);
1141        let data_len = reader.read_usize().unwrap();
1142        let data = reader.read_slice(data_len).unwrap().to_vec();
1143        assert!(!reader.has_more_bytes());
1144
1145        let mut malformed = Vec::new();
1146        malformed.write_usize(data_len + TRAILING.len());
1147        malformed.write_bytes(&data);
1148        malformed.write_bytes(TRAILING);
1149
1150        let error =
1151            DebugSourceNode::read_from(&mut miden_core::serde::SliceReader::new(&malformed))
1152                .unwrap_err();
1153        let DeserializationError::InvalidValue(message) = error else {
1154            panic!("expected InvalidValue error");
1155        };
1156        assert!(message.contains("but 2 remain in the buffer"), "{message}");
1157    }
1158
1159    fn roundtrip_debug_info(value: &PackageDebugInfo) -> PackageDebugInfo {
1160        let bytes = value.to_bytes();
1161        let result =
1162            PackageDebugInfo::read_from(&mut miden_core::serde::SliceReader::new(bytes.as_slice()))
1163                .unwrap();
1164        assert_eq!(result.version(), value.version());
1165        assert_eq!(result.strings(), value.strings());
1166        assert_eq!(result.files(), value.files());
1167        assert_eq!(result.locations(), value.locations());
1168        assert_eq!(result.types(), value.types());
1169        assert_eq!(result.functions(), value.functions());
1170        assert_eq!(result.nodes().as_slice(), value.nodes().as_slice());
1171        assert_eq!(result.roots(), value.roots());
1172        assert_eq!(result.error_messages(), value.error_messages());
1173        result
1174    }
1175
1176    #[test]
1177    fn test_debug_types_roundtrip() {
1178        let mut builder = PackageDebugInfoBuilder::default();
1179
1180        let i32_type_idx = builder.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I32));
1181        let felt_type_idx = builder.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt));
1182        builder.add_type(DebugTypeInfo::Pointer { pointee_type_idx: i32_type_idx });
1183        builder.add_type(DebugTypeInfo::Array {
1184            element_type_idx: felt_type_idx,
1185            count: Some(4),
1186        });
1187
1188        let x_idx = builder.add_string("x");
1189        let y_idx = builder.add_string("y");
1190        let point_idx = builder.add_string("Point");
1191        builder.add_type(DebugTypeInfo::Struct {
1192            name_idx: point_idx,
1193            size: 16,
1194            fields: alloc::vec![
1195                DebugFieldInfo {
1196                    name_idx: x_idx,
1197                    type_idx: felt_type_idx,
1198                    offset: 0,
1199                },
1200                DebugFieldInfo {
1201                    name_idx: y_idx,
1202                    type_idx: felt_type_idx,
1203                    offset: 8,
1204                },
1205            ],
1206        });
1207
1208        let status_idx = builder.add_string("Status");
1209        let ok_idx = builder.add_string("Ok");
1210        let err_idx = builder.add_string("Err");
1211        builder.add_type(DebugTypeInfo::Enum {
1212            name_idx: status_idx,
1213            size: 8,
1214            discriminant_type_idx: i32_type_idx,
1215            variants: alloc::vec![
1216                DebugVariantInfo {
1217                    name_idx: ok_idx,
1218                    type_idx: None,
1219                    payload_offset: None,
1220                    discriminant: 0,
1221                },
1222                DebugVariantInfo {
1223                    name_idx: err_idx,
1224                    type_idx: Some(felt_type_idx),
1225                    payload_offset: Some(8),
1226                    discriminant: 1,
1227                },
1228            ],
1229        });
1230
1231        let debug_info = *builder.build();
1232        let result = roundtrip_debug_info(&debug_info);
1233        assert_eq!(result.strings(), debug_info.strings());
1234        assert_eq!(result.types(), debug_info.types());
1235    }
1236
1237    #[test]
1238    fn test_debug_sources_roundtrip() {
1239        let mut builder = PackageDebugInfoBuilder::default();
1240        builder.add_file(Uri::new("test.rs"), None);
1241        builder.add_file(Uri::new("main.rs"), Some([42u8; 32]));
1242
1243        let debug_info = *builder.build();
1244        let result = roundtrip_debug_info(&debug_info);
1245        assert_eq!(result.strings(), debug_info.strings());
1246        assert_eq!(result.files(), debug_info.files());
1247        assert_eq!(result.files()[DebugFileIdx::from(1)].checksum(), Some(&[42u8; 32]));
1248    }
1249
1250    #[test]
1251    fn test_debug_functions_roundtrip() {
1252        let mut builder = PackageDebugInfoBuilder::default();
1253        let name_idx = builder.add_string("test_function");
1254        let file_idx = builder.add_file(Uri::new("test.masm"), None);
1255        let line = LineNumber::new(10).unwrap();
1256        let column = ColumnNumber::new(1).unwrap();
1257        builder.add_function(DebugFunctionInfo::new(
1258            None,
1259            name_idx,
1260            file_idx,
1261            line,
1262            column,
1263            Word::default(),
1264        ));
1265
1266        let debug_info = *builder.build();
1267        let result = roundtrip_debug_info(&debug_info);
1268        assert_eq!(result.functions(), debug_info.functions());
1269    }
1270
1271    #[test]
1272    fn debug_function_v4_wire_bytes_are_stable() {
1273        const EXPECTED_ROW: [u8; size_of::<WireDebugFunctionInfo>()] = [
1274            1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0,
1275            0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 13,
1276            0, 0, 0, 15, 0, 0, 0, 17, 0, 0, 0, 19, 0, 0, 0,
1277        ];
1278
1279        let function = DebugFunctionInfo {
1280            mast_root: Word::new([
1281                Felt::new(1).unwrap(),
1282                Felt::new(2).unwrap(),
1283                Felt::new(3).unwrap(),
1284                Felt::new(4).unwrap(),
1285            ]),
1286            source_node: Some(DebugSourceNodeId::from(7)).into(),
1287            type_idx: Some(DebugTypeIdx::from(9)).into(),
1288            linkage_name_idx: Some(DebugStringIdx::from(11)).into(),
1289            name_idx: DebugStringIdx::from(13),
1290            file_idx: DebugFileIdx::from(15),
1291            line: LineIndex::from(17),
1292            column: ColumnIndex::from(19),
1293        };
1294        let mut builder = PackageDebugInfoBuilder::default();
1295        builder.add_function(function);
1296        let debug_info = builder.build();
1297
1298        let bytes = debug_info.to_bytes();
1299        assert_eq!(bytes[0], 4);
1300        assert!(
1301            bytes.windows(EXPECTED_ROW.len()).any(|window| window == EXPECTED_ROW),
1302            "serialized debug info did not contain the expected function row",
1303        );
1304
1305        let decoded =
1306            PackageDebugInfo::read_from(&mut miden_core::serde::SliceReader::new(&bytes)).unwrap();
1307        assert_eq!(decoded.functions(), [function]);
1308    }
1309
1310    #[test]
1311    fn test_debug_source_graph_roundtrip() {
1312        let mut builder = PackageDebugInfoBuilder::default();
1313        let child = builder
1314            .add_node(DebugSourceNode {
1315                exec_node: MastNodeId::new_unchecked(0),
1316                children: alloc::vec![],
1317                op_start: 0,
1318                op_end: 1,
1319                asm_ops: alloc::vec![],
1320                debug_vars: alloc::vec![],
1321                inline_calls: alloc::vec![],
1322            })
1323            .unwrap();
1324        let root = builder
1325            .add_node(DebugSourceNode {
1326                exec_node: MastNodeId::new_unchecked(1),
1327                children: alloc::vec![child],
1328                op_start: 1,
1329                op_end: 3,
1330                asm_ops: alloc::vec![],
1331                debug_vars: alloc::vec![],
1332                inline_calls: alloc::vec![],
1333            })
1334            .unwrap();
1335        builder.add_root(root);
1336
1337        let debug_info = *builder.build();
1338        let result = roundtrip_debug_info(&debug_info);
1339        assert_eq!(result.nodes().as_slice(), debug_info.nodes().as_slice());
1340        assert_eq!(result.roots(), debug_info.roots());
1341    }
1342
1343    #[test]
1344    fn test_debug_source_metadata_roundtrip() {
1345        let mut builder = PackageDebugInfoBuilder::default();
1346        let location =
1347            Location::new(Uri::new("file://test.masm"), ByteIndex::new(10), ByteIndex::new(14));
1348        let location_idx = builder.add_location(location);
1349        let file_idx = builder.debug_info().locations()[location_idx].file_idx;
1350        let context_name_idx = builder.add_string("test::ctx");
1351        let op_name_idx = builder.add_string("add");
1352        let var_name_idx = builder.add_string("x");
1353        let function_name_idx = builder.add_string("callee");
1354        let function_idx = builder.add_function(DebugFunctionInfo::new(
1355            None,
1356            function_name_idx,
1357            file_idx,
1358            LineNumber::new(10).unwrap(),
1359            ColumnNumber::new(5).unwrap(),
1360            Word::default(),
1361        ));
1362
1363        let root = builder
1364            .add_node(DebugSourceNode {
1365                exec_node: MastNodeId::new_unchecked(0),
1366                children: alloc::vec![],
1367                op_start: 0,
1368                op_end: 3,
1369                asm_ops: alloc::vec![DebugSourceAsmOp::new(
1370                    2,
1371                    Some(location_idx),
1372                    context_name_idx,
1373                    op_name_idx,
1374                    1,
1375                )],
1376                debug_vars: alloc::vec![DebugSourceVar {
1377                    op_idx: 2,
1378                    name_idx: var_name_idx,
1379                    type_id: None,
1380                    arg_idx: None,
1381                    location_idx: None,
1382                    value_location: DebugVarLocation::Stack(0),
1383                }],
1384                inline_calls: alloc::vec![DebugSourceInlineCall {
1385                    op_idx: 2,
1386                    op_end: 3,
1387                    callee_idx: function_idx,
1388                    loc_idx: location_idx,
1389                }],
1390            })
1391            .unwrap();
1392        builder.add_root(root);
1393
1394        let debug_info = *builder.build();
1395        let result = roundtrip_debug_info(&debug_info);
1396        assert_eq!(result.nodes().as_slice(), debug_info.nodes().as_slice());
1397        assert_eq!(result.locations(), debug_info.locations());
1398        assert_eq!(result.functions(), debug_info.functions());
1399        assert_eq!(result.get_string(context_name_idx).as_deref(), Some("test::ctx"));
1400        assert_eq!(result.get_location(location_idx), debug_info.get_location(location_idx));
1401    }
1402
1403    #[test]
1404    fn test_debug_source_locations_are_deduplicated() {
1405        let mut builder = PackageDebugInfoBuilder::default();
1406        let location =
1407            Location::new(Uri::new("file://test.masm"), ByteIndex::new(10), ByteIndex::new(14));
1408        let first_location_idx = builder.add_location(location.clone());
1409        let second_location_idx = builder.add_location(location);
1410        assert_eq!(first_location_idx, second_location_idx);
1411        assert_eq!(builder.debug_info().locations().len(), 1);
1412
1413        let context_name_idx = builder.add_string("test::ctx");
1414        let push_name_idx = builder.add_string("push.1");
1415        let add_name_idx = builder.add_string("add");
1416        let root = builder
1417            .add_node(DebugSourceNode {
1418                exec_node: MastNodeId::new_unchecked(0),
1419                children: alloc::vec![],
1420                op_start: 0,
1421                op_end: 2,
1422                asm_ops: alloc::vec![
1423                    DebugSourceAsmOp::new(
1424                        0,
1425                        Some(first_location_idx),
1426                        context_name_idx,
1427                        push_name_idx,
1428                        1,
1429                    ),
1430                    DebugSourceAsmOp::new(
1431                        1,
1432                        Some(second_location_idx),
1433                        context_name_idx,
1434                        add_name_idx,
1435                        1,
1436                    ),
1437                ],
1438                debug_vars: alloc::vec![],
1439                inline_calls: alloc::vec![],
1440            })
1441            .unwrap();
1442        builder.add_root(root);
1443
1444        let debug_info = *builder.build();
1445        let result = roundtrip_debug_info(&debug_info);
1446        assert_eq!(result.locations().len(), 1);
1447        assert_eq!(
1448            result.source_node(root).unwrap().asm_ops,
1449            debug_info.source_node(root).unwrap().asm_ops
1450        );
1451    }
1452
1453    #[test]
1454    fn test_debug_source_strings_are_deduplicated() {
1455        let mut builder = PackageDebugInfoBuilder::default();
1456        let context_name_idx = builder.add_string("test::ctx");
1457        let same_context_name_idx = builder.add_string("test::ctx");
1458        let add_name_idx = builder.add_string("add");
1459        let same_add_name_idx = builder.add_string("add");
1460        let mul_name_idx = builder.add_string("mul");
1461        let other_context_idx = builder.add_string("test::other");
1462        assert_eq!(context_name_idx, same_context_name_idx);
1463        assert_eq!(add_name_idx, same_add_name_idx);
1464
1465        let root = builder
1466            .add_node(DebugSourceNode {
1467                exec_node: MastNodeId::new_unchecked(0),
1468                children: alloc::vec![],
1469                op_start: 0,
1470                op_end: 3,
1471                asm_ops: alloc::vec![
1472                    DebugSourceAsmOp::new(0, None, context_name_idx, add_name_idx, 1,),
1473                    DebugSourceAsmOp::new(1, None, same_context_name_idx, mul_name_idx, 1,),
1474                    DebugSourceAsmOp::new(2, None, other_context_idx, same_add_name_idx, 1,),
1475                ],
1476                debug_vars: alloc::vec![],
1477                inline_calls: alloc::vec![],
1478            })
1479            .unwrap();
1480        builder.add_root(root);
1481
1482        let debug_info = *builder.build();
1483        let result = roundtrip_debug_info(&debug_info);
1484        assert_eq!(result.strings(), debug_info.strings());
1485        assert_eq!(
1486            result.source_node(root).unwrap().asm_ops,
1487            debug_info.source_node(root).unwrap().asm_ops
1488        );
1489    }
1490
1491    #[test]
1492    fn test_debug_error_messages_roundtrip() {
1493        let mut builder = PackageDebugInfoBuilder::default();
1494        assert!(builder.add_error_message(42, Arc::from("assertion message")));
1495
1496        let debug_info = *builder.build();
1497        let result = roundtrip_debug_info(&debug_info);
1498        assert_eq!(result.error_messages(), debug_info.error_messages());
1499        assert_eq!(result.error_message(42).as_deref(), Some("assertion message"));
1500    }
1501
1502    #[test]
1503    fn test_empty_debug_info_roundtrip() {
1504        let debug_info = PackageDebugInfo::default();
1505        let result = roundtrip_debug_info(&debug_info);
1506        assert!(result.strings().is_empty());
1507        assert!(result.files().is_empty());
1508        assert!(result.locations().is_empty());
1509        assert!(result.types().is_empty());
1510        assert!(result.functions().is_empty());
1511        assert!(result.nodes().is_empty());
1512        assert!(result.roots().is_empty());
1513        assert!(result.error_messages().is_empty());
1514    }
1515
1516    #[test]
1517    fn test_all_primitive_types_roundtrip() {
1518        let mut builder = PackageDebugInfoBuilder::default();
1519
1520        for primitive in [
1521            DebugPrimitiveType::Void,
1522            DebugPrimitiveType::Bool,
1523            DebugPrimitiveType::I8,
1524            DebugPrimitiveType::U8,
1525            DebugPrimitiveType::I16,
1526            DebugPrimitiveType::U16,
1527            DebugPrimitiveType::I32,
1528            DebugPrimitiveType::U32,
1529            DebugPrimitiveType::I64,
1530            DebugPrimitiveType::U64,
1531            DebugPrimitiveType::I128,
1532            DebugPrimitiveType::U128,
1533            DebugPrimitiveType::F32,
1534            DebugPrimitiveType::F64,
1535            DebugPrimitiveType::Felt,
1536            DebugPrimitiveType::Word,
1537            DebugPrimitiveType::U256,
1538        ] {
1539            builder.add_type(DebugTypeInfo::Primitive(primitive));
1540        }
1541
1542        let debug_info = *builder.build();
1543        let result = roundtrip_debug_info(&debug_info);
1544        assert_eq!(result.types(), debug_info.types());
1545    }
1546
1547    #[test]
1548    fn test_function_type_roundtrip() {
1549        let ty = DebugTypeInfo::Function {
1550            return_type_idx: Some(DebugTypeIdx::from(0)),
1551            param_type_indices: alloc::vec![
1552                DebugTypeIdx::from(1),
1553                DebugTypeIdx::from(2),
1554                DebugTypeIdx::from(3)
1555            ],
1556        };
1557        roundtrip(&ty);
1558
1559        let void_fn = DebugTypeInfo::Function {
1560            return_type_idx: None,
1561            param_type_indices: alloc::vec![],
1562        };
1563        roundtrip(&void_fn);
1564    }
1565
1566    #[test]
1567    fn test_file_info_with_checksum_roundtrip() {
1568        let file = DebugFileInfo::new(DebugStringIdx::from(0)).with_checksum([42u8; 32]);
1569        roundtrip(&file);
1570    }
1571
1572    #[test]
1573    fn test_debug_info_v2_is_rejected() {
1574        let bytes = [2];
1575        let mut reader = miden_core::serde::SliceReader::new(&bytes);
1576        let error = PackageDebugInfo::read_from(&mut reader).unwrap_err();
1577        let DeserializationError::InvalidValue(message) = error else {
1578            panic!("expected InvalidValue error");
1579        };
1580        assert!(message.contains("unsupported debug_info version: 2"));
1581    }
1582
1583    #[test]
1584    fn inline_range_wire_bytes_are_stable_and_reversed_ranges_are_rejected() {
1585        let row = DebugSourceInlineCall {
1586            op_idx: 2,
1587            op_end: 100_000,
1588            callee_idx: DebugFunctionIdx::from(3),
1589            loc_idx: DebugLocIdx::from(4),
1590        };
1591        let bytes = row.to_bytes();
1592        assert_eq!(bytes, [2, 0, 0, 0, 160, 134, 1, 0, 3, 0, 0, 0, 4, 0, 0, 0]);
1593        assert_eq!(DebugSourceInlineCall::read_from_bytes(&bytes).unwrap(), row);
1594        assert!(row.contains_operation(2));
1595        assert!(row.contains_operation(99_999));
1596        assert!(!row.contains_operation(100_000));
1597        let mut invalid = bytes;
1598        invalid[4..8].copy_from_slice(&1u32.to_le_bytes());
1599        assert!(DebugSourceInlineCall::read_from_bytes(&invalid).is_err());
1600        assert!(PackageDebugInfo::read_from_bytes(&[3]).is_err());
1601    }
1602
1603    #[test]
1604    fn long_inline_ranges_roundtrip_without_expanding_into_operation_rows() {
1605        let mut builder = PackageDebugInfoBuilder::default();
1606        let source_id = builder
1607            .add_node(DebugSourceNode {
1608                exec_node: MastNodeId::new_unchecked(0),
1609                children: Vec::new(),
1610                op_start: 0,
1611                op_end: 1_000_000,
1612                asm_ops: Vec::new(),
1613                debug_vars: Vec::new(),
1614                inline_calls: (0..7)
1615                    .map(|index| DebugSourceInlineCall {
1616                        op_idx: 0,
1617                        op_end: 1_000_000,
1618                        callee_idx: DebugFunctionIdx::from(index),
1619                        loc_idx: DebugLocIdx::from(index),
1620                    })
1621                    .collect(),
1622            })
1623            .unwrap();
1624        let bytes = builder.build().to_bytes();
1625        assert!(bytes.len() < 256);
1626        let decoded = PackageDebugInfo::read_from_bytes(&bytes).unwrap();
1627        assert_eq!(decoded.source_node(source_id).unwrap().inline_calls.len(), 7);
1628        assert_eq!(decoded.inline_calls_for_operation(source_id, 999_999).count(), 7);
1629        assert_eq!(decoded.inline_calls_for_operation(source_id, 1_000_000).count(), 0);
1630    }
1631
1632    #[test]
1633    fn test_debug_info_payload_bounds() {
1634        let bytes = PackageDebugInfo::default().to_bytes();
1635
1636        let mut reader = FixedBudgetReader::new(&bytes, 1);
1637        let error = PackageDebugInfo::read_from(&mut reader).unwrap_err();
1638        let DeserializationError::InvalidValue(message) = error else {
1639            panic!("expected InvalidValue error");
1640        };
1641        assert!(message.contains("package debug info"));
1642        assert!(message.contains("exceeds budget"));
1643
1644        let mut reader = FixedBudgetReader::new(&bytes, 1);
1645        let error = PackageDebugInfo::read_from_unmetered(&mut reader).unwrap_err();
1646        let DeserializationError::InvalidValue(message) = error else {
1647            panic!("expected InvalidValue error");
1648        };
1649        assert!(message.contains("package debug info"));
1650        assert!(message.contains("exceeds budget"));
1651
1652        let mut reader = FixedBudgetReader::new(&bytes, bytes.len());
1653        let result = PackageDebugInfo::read_from(&mut reader).unwrap();
1654        assert!(result.nodes().is_empty());
1655    }
1656
1657    #[test]
1658    fn checked_encoding_matches_standard_decoder_limits() {
1659        let mut builder = PackageDebugInfoBuilder::default();
1660        builder.add_string("hello");
1661        builder.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt));
1662        let debug = builder.build();
1663        let bytes = debug.to_bytes_with_standard_limits().unwrap();
1664        assert_eq!(bytes, debug.to_bytes());
1665        assert_eq!(PackageDebugInfo::read_from_bytes(&bytes).unwrap(), *debug);
1666
1667        for limits in [
1668            PackageDebugInfoDecodeLimits {
1669                payload_size: 0,
1670                ..PackageDebugInfoDecodeLimits::BOUNDED
1671            },
1672            PackageDebugInfoDecodeLimits {
1673                string_rows: 0,
1674                ..PackageDebugInfoDecodeLimits::BOUNDED
1675            },
1676            PackageDebugInfoDecodeLimits {
1677                string_size: 0,
1678                ..PackageDebugInfoDecodeLimits::BOUNDED
1679            },
1680            PackageDebugInfoDecodeLimits {
1681                type_rows: 0,
1682                ..PackageDebugInfoDecodeLimits::BOUNDED
1683            },
1684        ] {
1685            let encode_error = DeserializationError::InvalidValue(
1686                debug.to_bytes_with_limits(limits).unwrap_err().to_string(),
1687            );
1688            let mut reader = miden_core::serde::SliceReader::new(&bytes);
1689            let decode_error =
1690                PackageDebugInfo::read_from_with_limits(&mut reader, limits).unwrap_err();
1691            assert_eq!(encode_error.to_string(), decode_error.to_string());
1692        }
1693    }
1694
1695    #[test]
1696    fn package_debug_info_decodes_merged_dependency_tables() {
1697        // Dynamic exec currently imports entire dependency tables. Each dependency is readable
1698        // on its own, but their distinct strings together exceed the former 32 MiB limit.
1699        const STRING_SIZE: usize = 4096;
1700        const STRING_COUNT: usize = 18 * 1024 * 1024 / STRING_SIZE;
1701        let mut merged = PackageDebugInfoBuilder::default();
1702        for dependency in ['a', 'b'] {
1703            let mut builder = PackageDebugInfoBuilder::default();
1704            for index in 0..STRING_COUNT {
1705                let mut prefix = alloc::format!("{dependency}:{index:04}");
1706                let padding = "x".repeat(STRING_SIZE - prefix.len());
1707                prefix.push_str(&padding);
1708                builder.add_string(prefix);
1709            }
1710            let bytes = builder.build().to_bytes();
1711            let debug = PackageDebugInfo::read_from_bytes(&bytes)
1712                .expect("each dependency's debug tables must be readable");
1713            debug.merge_tables_into(&mut merged).unwrap();
1714        }
1715        let merged = merged.build();
1716        let bytes = merged.to_bytes();
1717        let decoded = PackageDebugInfo::read_from_bytes(&bytes)
1718            .expect("merged dependency debug tables must remain readable");
1719        assert_eq!(decoded.strings().len(), 2 * STRING_COUNT);
1720        assert_eq!(decoded, *merged);
1721    }
1722
1723    #[test]
1724    fn package_debug_info_decodes_merged_dependency_string_rows() {
1725        let mut merged = PackageDebugInfoBuilder::default();
1726        for dependency in ['a', 'b'] {
1727            let mut builder = PackageDebugInfoBuilder::default();
1728            for index in 0..60_000 {
1729                builder.add_string(alloc::format!("{dependency}:{index}"));
1730            }
1731            let bytes = builder.build().to_bytes();
1732            let debug = PackageDebugInfo::read_from_bytes(&bytes).unwrap();
1733            debug.merge_tables_into(&mut merged).unwrap();
1734        }
1735        let merged = merged.build();
1736        let decoded = PackageDebugInfo::read_from_bytes(&merged.to_bytes())
1737            .expect("merged string tables from readable dependencies must remain readable");
1738        assert_eq!(decoded.strings().len(), 120_000);
1739        assert_eq!(decoded, *merged);
1740    }
1741
1742    #[test]
1743    fn package_debug_info_rejects_payload_over_standard_limit() {
1744        let payload_size = MAX_DEBUG_INFO_PAYLOAD_SIZE + 1;
1745        let mut bytes = Vec::new();
1746        bytes.write_u8(DEBUG_INFO_VERSION);
1747        bytes.write_usize(payload_size);
1748        bytes.resize(bytes.len() + payload_size, 0);
1749
1750        let error = PackageDebugInfo::read_from_bytes(&bytes).unwrap_err();
1751        let DeserializationError::InvalidValue(message) = error else {
1752            panic!("expected InvalidValue error");
1753        };
1754        assert!(message.contains("package debug info payload size"), "{message}");
1755        assert!(message.contains("exceeds limit"), "{message}");
1756    }
1757
1758    #[test]
1759    fn unmetered_package_debug_info_decode_ignores_fixed_limits() {
1760        let oversized_string = "x".repeat(MAX_DEBUG_INFO_STRING_SIZE + 1);
1761        let mut builder = PackageDebugInfoBuilder::default();
1762        builder.add_string(oversized_string.clone());
1763        let bytes = builder.build().to_bytes();
1764
1765        let error = PackageDebugInfo::read_from_bytes(&bytes).unwrap_err();
1766        let DeserializationError::InvalidValue(message) = error else {
1767            panic!("expected InvalidValue error");
1768        };
1769        assert!(message.contains("debug string size"));
1770        assert!(message.contains("exceeds limit"));
1771
1772        let decoded = PackageDebugInfo::read_from_bytes_unmetered(&bytes).unwrap();
1773        assert_eq!(decoded.strings().len(), 1);
1774        assert_eq!(decoded.strings()[DebugStringIdx::from(0)].as_ref(), oversized_string);
1775    }
1776
1777    #[test]
1778    fn checked_debug_encoding_matches_normal_encoding_for_readable_data() {
1779        let mut builder = PackageDebugInfoBuilder::default();
1780        builder.add_string("source.rs");
1781        let info = builder.build();
1782        let bytes = info.to_bytes_with_standard_limits().unwrap();
1783        assert_eq!(bytes, info.to_bytes());
1784        assert!(PackageDebugInfo::read_from_bytes(&bytes).is_ok());
1785    }
1786
1787    #[test]
1788    fn checked_debug_encoding_rejects_oversized_strings() {
1789        let mut builder = PackageDebugInfoBuilder::default();
1790        builder.add_string("x".repeat(MAX_DEBUG_INFO_STRING_SIZE + 1));
1791        assert_matches!(
1792            builder.build().to_bytes_with_standard_limits(),
1793            Err(super::super::DebugInfoEncodingError::LimitExceeded {
1794                resource: "debug string size",
1795                ..
1796            })
1797        );
1798    }
1799
1800    #[test]
1801    fn checked_debug_encoding_rejects_oversized_payloads() {
1802        let mut builder = PackageDebugInfoBuilder::default();
1803        for index in 0..((MAX_DEBUG_INFO_PAYLOAD_SIZE / MAX_DEBUG_INFO_STRING_SIZE) + 1) {
1804            let mut string = format!("{index:08}");
1805            string.push_str(&"x".repeat(MAX_DEBUG_INFO_STRING_SIZE - string.len()));
1806            builder.add_string(string);
1807        }
1808        let info = builder.build();
1809        assert_matches!(
1810            info.to_bytes_with_standard_limits(),
1811            Err(super::super::DebugInfoEncodingError::LimitExceeded {
1812                resource: "package debug info payload size",
1813                ..
1814            })
1815        );
1816        assert!(PackageDebugInfo::read_from_bytes(&info.to_bytes()).is_err());
1817    }
1818
1819    #[test]
1820    fn test_debug_info_rejects_truncated_string_table() {
1821        let mut payload = Vec::new();
1822        payload.write_usize(2);
1823
1824        let mut bytes = Vec::new();
1825        bytes.write_u8(DEBUG_INFO_VERSION);
1826        bytes.write_usize(payload.len());
1827        bytes.write_bytes(&payload);
1828
1829        let mut reader = miden_core::serde::SliceReader::new(&bytes);
1830        let error = PackageDebugInfo::read_from(&mut reader).unwrap_err();
1831        let DeserializationError::InvalidValue(message) = error else {
1832            panic!("expected InvalidValue error");
1833        };
1834        assert!(message.contains("debug_info strings count 2"));
1835        assert!(message.contains("exceeds budget"));
1836    }
1837
1838    #[test]
1839    fn test_function_params_bounds() {
1840        let too_many = function_type_bytes(2);
1841        let mut reader = FixedBudgetReader::new(&too_many, 4);
1842        let error = DebugTypeInfo::read_from(&mut reader).unwrap_err();
1843        assert!(matches!(error, DeserializationError::InvalidValue(_)));
1844
1845        let ok = function_type_bytes(1);
1846        let mut reader = FixedBudgetReader::new(&ok, 4);
1847        let ty = DebugTypeInfo::read_from(&mut reader).unwrap();
1848        match ty {
1849            DebugTypeInfo::Function { param_type_indices, .. } => {
1850                assert_eq!(param_type_indices.len(), 1);
1851            },
1852            _ => panic!("expected function type"),
1853        }
1854    }
1855}