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, 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
28const 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#[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#[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 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 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 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
286 Self::read_from_with_limits(source, PackageDebugInfoDecodeLimits::BOUNDED)
287 }
288
289 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
296 Self::read_from(&mut miden_core::serde::SliceReader::new(bytes))
297 }
298}
299
300#[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
392impl 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
433impl 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
457const 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 1
597 }
598}
599
600impl 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
620impl 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 DebugStringIdx::min_serialized_size()
666 + 2 * u8::min_serialized_size()
667 + 2 * u64::min_serialized_size()
668 }
669}
670
671impl 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
697struct 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 let ptr = unsafe { alloc::alloc::alloc(layout) };
720 let Some(ptr) = NonNull::new(ptr) else {
721 alloc::alloc::handle_alloc_error(layout)
722 };
723 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 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 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}