Skip to main content

miden_mast_package/debug_info/
serialization.rs

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