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