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