1use alloc::{format, string::ToString, sync::Arc, vec::Vec};
2use core::{fmt, num::NonZeroU32};
3
4use miden_core::serde::{
5 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, read_bounded_len,
6};
7use miden_debug_types::Location;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 Felt,
13 ast::{TypeExpr, types::Type},
14};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct DebugVarInfo {
25 name: Arc<str>,
27 ty: Option<Type>,
29 declared_type: Option<Arc<TypeExpr>>,
31 arg_index: Option<NonZeroU32>,
33 location: Option<Location>,
37 value_location: DebugVarLocation,
39}
40
41impl DebugVarInfo {
42 pub fn new(name: impl Into<Arc<str>>, value_location: DebugVarLocation) -> Self {
44 Self {
45 name: name.into(),
46 ty: None,
47 declared_type: None,
48 arg_index: None,
49 location: None,
50 value_location,
51 }
52 }
53
54 pub fn name(&self) -> &Arc<str> {
56 &self.name
57 }
58
59 pub fn ty(&self) -> Option<&Type> {
61 self.ty.as_ref()
62 }
63
64 pub fn declared_type(&self) -> Option<Arc<TypeExpr>> {
66 self.declared_type.clone()
67 }
68
69 pub fn set_ty(&mut self, ty: Type, declared_type: Option<Arc<TypeExpr>>) {
71 self.ty = Some(ty);
72 self.declared_type = declared_type;
73 }
74
75 pub fn arg_index(&self) -> Option<NonZeroU32> {
78 self.arg_index
79 }
80
81 pub fn set_arg_index(&mut self, arg_index: u32) {
86 self.arg_index =
87 Some(NonZeroU32::new(arg_index).expect("argument index must be 1-based (non-zero)"));
88 }
89
90 pub fn location(&self) -> Option<&Location> {
93 self.location.as_ref()
94 }
95
96 pub fn set_location(&mut self, location: Location) {
100 self.location = Some(location);
101 }
102
103 pub fn value_location(&self) -> &DebugVarLocation {
105 &self.value_location
106 }
107
108 pub fn set_value_location(&mut self, value_location: DebugVarLocation) {
110 self.value_location = value_location;
111 }
112}
113
114impl fmt::Display for DebugVarInfo {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "var.{}", self.name)?;
117
118 if let Some(arg_index) = self.arg_index {
119 write!(f, "[arg{arg_index}]")?;
120 }
121
122 write!(f, " = {}", self.value_location)?;
123
124 if let Some(loc) = &self.location {
125 write!(f, " [{}@{}..{}]", loc.uri, loc.start, loc.end)?;
126 }
127
128 Ok(())
129 }
130}
131
132#[derive(Clone, Copy, Debug, Eq, PartialEq)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub enum DebugFrameBase {
139 Local(i16),
141 Memory(u32),
143}
144
145#[derive(Clone, Debug, Eq, PartialEq)]
151#[cfg_attr(feature = "serde", derive(Serialize))]
152#[cfg_attr(
153 all(feature = "arbitrary", test),
154 miden_test_serialization_macros::serialization_test
155)]
156pub struct DebugLocationExpression {
157 operations: Vec<DebugLocationExpressionOp>,
158}
159
160#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
162#[error(
163 "debug location expression has {operation_count} operations, but at most {MAX_DEBUG_LOCATION_EXPRESSION_OPS} are supported"
164)]
165pub struct DebugLocationExpressionError {
166 operation_count: usize,
167}
168
169impl DebugLocationExpressionError {
170 pub fn operation_count(&self) -> usize {
172 self.operation_count
173 }
174}
175
176#[cfg(feature = "serde")]
177#[derive(Deserialize)]
178struct DebugLocationExpressionSerde {
179 #[serde(deserialize_with = "deserialize_debug_location_expression_operations")]
180 operations: Vec<DebugLocationExpressionOp>,
181}
182
183#[cfg(feature = "serde")]
184fn deserialize_debug_location_expression_operations<'de, D>(
185 deserializer: D,
186) -> Result<Vec<DebugLocationExpressionOp>, D::Error>
187where
188 D: serde::Deserializer<'de>,
189{
190 struct OperationsVisitor;
191
192 impl<'de> serde::de::Visitor<'de> for OperationsVisitor {
193 type Value = Vec<DebugLocationExpressionOp>;
194
195 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
196 write!(
197 formatter,
198 "at most {MAX_DEBUG_LOCATION_EXPRESSION_OPS} debug location operations"
199 )
200 }
201
202 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
203 where
204 A: serde::de::SeqAccess<'de>,
205 {
206 if let Some(operation_count) = sequence.size_hint()
207 && operation_count > MAX_DEBUG_LOCATION_EXPRESSION_OPS
208 {
209 return Err(serde::de::Error::custom(DebugLocationExpressionError {
210 operation_count,
211 }));
212 }
213
214 let mut operations = Vec::with_capacity(sequence.size_hint().unwrap_or(0).min(8));
215 while let Some(operation) = sequence.next_element()? {
216 if operations.len() == MAX_DEBUG_LOCATION_EXPRESSION_OPS {
217 return Err(serde::de::Error::custom(DebugLocationExpressionError {
218 operation_count: operations.len() + 1,
219 }));
220 }
221 operations.push(operation);
222 }
223 Ok(operations)
224 }
225 }
226
227 deserializer.deserialize_seq(OperationsVisitor)
228}
229
230const MAX_DEBUG_LOCATION_EXPRESSION_OPS: usize = 256;
231
232impl DebugLocationExpression {
233 pub fn new(
240 operations: Vec<DebugLocationExpressionOp>,
241 ) -> Result<Self, DebugLocationExpressionError> {
242 validate_debug_location_expression_len(operations.len())?;
243 Ok(Self { operations })
244 }
245
246 pub fn operations(&self) -> &[DebugLocationExpressionOp] {
248 &self.operations
249 }
250
251 pub fn is_empty(&self) -> bool {
253 self.operations.is_empty()
254 }
255}
256
257#[cfg(feature = "serde")]
258impl<'de> Deserialize<'de> for DebugLocationExpression {
259 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
260 where
261 D: serde::Deserializer<'de>,
262 {
263 let expression = DebugLocationExpressionSerde::deserialize(deserializer)?;
264 Self::new(expression.operations).map_err(serde::de::Error::custom)
265 }
266}
267
268#[derive(Clone, Copy, Debug, Eq, PartialEq)]
275#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
276#[cfg_attr(
277 all(feature = "arbitrary", test),
278 miden_test_serialization_macros::serialization_test
279)]
280pub enum DebugLocationExpressionOp {
281 ReadStack(u8),
283 ReadMemory(u32),
285 ReadLocal(i16),
287 ConstU64(u64),
289 ConstI64(i64),
291 AddUnsigned(u64),
293 Add,
295 Sub,
297 DerefBytes,
300 FrameBaseAddress {
302 base: DebugFrameBase,
304 byte_offset: i64,
306 },
307}
308
309#[derive(Clone, Debug, Eq, PartialEq)]
315#[cfg_attr(
316 all(feature = "arbitrary", test),
317 miden_test_serialization_macros::serialization_test
318)]
319pub enum DebugVarLocation {
320 Stack(u8),
322 Memory(u32),
324 Const(Felt),
326 Local(i16),
332 Unavailable,
334 ResolvedFrameBase {
340 base: DebugFrameBase,
342 byte_offset: i64,
344 },
345 Expression(DebugLocationExpression),
347}
348
349impl fmt::Display for DebugVarLocation {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 match self {
352 Self::Stack(pos) => write!(f, "stack[{pos}]"),
353 Self::Memory(addr) => write!(f, "mem[{addr}]"),
354 Self::Const(val) => write!(f, "const({})", val.as_canonical_u64()),
355 Self::Local(offset) => write!(f, "FMP{offset:+}"),
356 Self::Unavailable => f.write_str("unavailable"),
357 Self::ResolvedFrameBase { base, byte_offset } => match base {
358 DebugFrameBase::Local(offset) => {
359 write!(f, "frame-base(FMP{offset:+}){byte_offset:+}")
360 },
361 DebugFrameBase::Memory(address) => {
362 write!(f, "frame-base(mem[{address}]){byte_offset:+}")
363 },
364 },
365 Self::Expression(expression) => {
366 f.write_str("expr(")?;
367 f.debug_list().entries(expression.operations()).finish()?;
368 f.write_str(")")
369 },
370 }
371 }
372}
373
374impl Serializable for DebugVarLocation {
378 fn write_into<W: ByteWriter>(&self, target: &mut W) {
379 match self {
380 Self::Stack(pos) => {
381 target.write_u8(0);
382 target.write_u8(*pos);
383 },
384 Self::Memory(addr) => {
385 target.write_u8(1);
386 target.write_u32(*addr);
387 },
388 Self::Const(felt) => {
389 target.write_u8(2);
390 target.write_u64(felt.as_canonical_u64());
391 },
392 Self::Local(offset) => {
393 target.write_u8(3);
394 target.write_bytes(&offset.to_le_bytes());
395 },
396 Self::Unavailable => {
397 target.write_u8(4);
398 },
399 Self::ResolvedFrameBase { base, byte_offset } => {
400 target.write_u8(5);
401 write_debug_frame_base(*base, target);
402 target.write_bytes(&byte_offset.to_le_bytes());
403 },
404 Self::Expression(expression) => {
405 target.write_u8(6);
406 expression.write_into(target);
407 },
408 }
409 }
410}
411
412impl Deserializable for DebugVarLocation {
413 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
414 let tag = source.read_u8()?;
415 match tag {
416 0 => Ok(Self::Stack(source.read_u8()?)),
417 1 => Ok(Self::Memory(source.read_u32()?)),
418 2 => {
419 let value = source.read_u64()?;
420 Ok(Self::Const(Felt::new_unchecked(value)))
421 },
422 3 => {
423 let bytes = source.read_array::<2>()?;
424 Ok(Self::Local(i16::from_le_bytes(bytes)))
425 },
426 4 => Ok(Self::Unavailable),
427 5 => {
428 let base = read_debug_frame_base(source)?;
429 let bytes = source.read_array::<8>()?;
430 let byte_offset = i64::from_le_bytes(bytes);
431 Ok(Self::ResolvedFrameBase { base, byte_offset })
432 },
433 6 => Ok(Self::Expression(DebugLocationExpression::read_from(source)?)),
434 _ => Err(DeserializationError::InvalidValue(format!(
435 "invalid DebugVarLocation tag: {tag}"
436 ))),
437 }
438 }
439
440 fn min_serialized_size() -> usize {
441 u8::min_serialized_size()
443 }
444}
445
446impl Serializable for DebugLocationExpression {
447 fn write_into<W: ByteWriter>(&self, target: &mut W) {
448 target.write_usize(self.operations.len());
449 for operation in &self.operations {
450 operation.write_into(target);
451 }
452 }
453}
454
455impl Deserializable for DebugLocationExpression {
456 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
457 let count = read_bounded_len(source, "debug location expression operations", 1)?;
458 validate_debug_location_expression_len(count)
459 .map_err(|error| DeserializationError::InvalidValue(error.to_string()))?;
460 let mut operations = Vec::with_capacity(count.min(8));
461 for _ in 0..count {
462 operations.push(DebugLocationExpressionOp::read_from(source)?);
463 }
464 Ok(Self { operations })
465 }
466
467 fn min_serialized_size() -> usize {
468 usize::min_serialized_size()
469 }
470}
471
472fn validate_debug_location_expression_len(
473 operation_count: usize,
474) -> Result<(), DebugLocationExpressionError> {
475 if operation_count > MAX_DEBUG_LOCATION_EXPRESSION_OPS {
476 return Err(DebugLocationExpressionError { operation_count });
477 }
478 Ok(())
479}
480
481impl Serializable for DebugLocationExpressionOp {
482 fn write_into<W: ByteWriter>(&self, target: &mut W) {
483 match self {
484 Self::ReadStack(position) => {
485 target.write_u8(0);
486 target.write_u8(*position);
487 },
488 Self::ReadMemory(address) => {
489 target.write_u8(1);
490 target.write_u32(*address);
491 },
492 Self::ReadLocal(offset) => {
493 target.write_u8(2);
494 target.write_bytes(&offset.to_le_bytes());
495 },
496 Self::ConstU64(value) => {
497 target.write_u8(3);
498 target.write_u64(*value);
499 },
500 Self::ConstI64(value) => {
501 target.write_u8(4);
502 target.write_bytes(&value.to_le_bytes());
503 },
504 Self::AddUnsigned(value) => {
505 target.write_u8(5);
506 target.write_u64(*value);
507 },
508 Self::Add => target.write_u8(6),
509 Self::Sub => target.write_u8(7),
510 Self::DerefBytes => target.write_u8(8),
511 Self::FrameBaseAddress { base, byte_offset } => {
512 target.write_u8(9);
513 write_debug_frame_base(*base, target);
514 target.write_bytes(&byte_offset.to_le_bytes());
515 },
516 }
517 }
518}
519
520impl Deserializable for DebugLocationExpressionOp {
521 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
522 match source.read_u8()? {
523 0 => Ok(Self::ReadStack(source.read_u8()?)),
524 1 => Ok(Self::ReadMemory(source.read_u32()?)),
525 2 => Ok(Self::ReadLocal(i16::from_le_bytes(source.read_array::<2>()?))),
526 3 => Ok(Self::ConstU64(source.read_u64()?)),
527 4 => Ok(Self::ConstI64(i64::from_le_bytes(source.read_array::<8>()?))),
528 5 => Ok(Self::AddUnsigned(source.read_u64()?)),
529 6 => Ok(Self::Add),
530 7 => Ok(Self::Sub),
531 8 => Ok(Self::DerefBytes),
532 9 => {
533 let base = read_debug_frame_base(source)?;
534 let byte_offset = i64::from_le_bytes(source.read_array::<8>()?);
535 Ok(Self::FrameBaseAddress { base, byte_offset })
536 },
537 tag => Err(DeserializationError::InvalidValue(format!(
538 "invalid DebugLocationExpressionOp tag: {tag}"
539 ))),
540 }
541 }
542
543 fn min_serialized_size() -> usize {
544 u8::min_serialized_size()
545 }
546}
547
548fn write_debug_frame_base<W: ByteWriter>(base: DebugFrameBase, target: &mut W) {
549 match base {
550 DebugFrameBase::Local(offset) => {
551 target.write_u8(0);
552 target.write_bytes(&offset.to_le_bytes());
553 },
554 DebugFrameBase::Memory(address) => {
555 target.write_u8(1);
556 target.write_u32(address);
557 },
558 }
559}
560
561fn read_debug_frame_base<R: ByteReader>(
562 source: &mut R,
563) -> Result<DebugFrameBase, DeserializationError> {
564 match source.read_u8()? {
565 0 => Ok(DebugFrameBase::Local(i16::from_le_bytes(source.read_array::<2>()?))),
566 1 => Ok(DebugFrameBase::Memory(source.read_u32()?)),
567 tag => Err(DeserializationError::InvalidValue(format!(
568 "invalid resolved debug frame-base tag: {tag}"
569 ))),
570 }
571}
572
573#[cfg(any(test, feature = "arbitrary"))]
577mod arbitrary {
578 use proptest::prelude::*;
579
580 use super::{
581 DebugFrameBase, DebugLocationExpression, DebugLocationExpressionOp, DebugVarLocation,
582 };
583 use crate::Felt;
584
585 impl Arbitrary for DebugFrameBase {
586 type Parameters = ();
587 type Strategy = BoxedStrategy<Self>;
588
589 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
590 prop_oneof![
591 any::<i16>().prop_map(Self::Local).boxed(),
592 any::<u32>().prop_map(Self::Memory).boxed(),
593 ]
594 .boxed()
595 }
596 }
597
598 impl Arbitrary for DebugLocationExpressionOp {
599 type Parameters = ();
600 type Strategy = BoxedStrategy<Self>;
601
602 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
603 prop_oneof![
605 any::<u8>().prop_map(Self::ReadStack).boxed(),
606 any::<u32>().prop_map(Self::ReadMemory).boxed(),
607 any::<i16>().prop_map(Self::ReadLocal).boxed(),
608 any::<u64>().prop_map(Self::ConstU64).boxed(),
609 any::<i64>().prop_map(Self::ConstI64).boxed(),
610 any::<u64>().prop_map(Self::AddUnsigned).boxed(),
611 Just(Self::Add),
612 Just(Self::Sub),
613 Just(Self::DerefBytes),
614 (any::<DebugFrameBase>(), any::<i64>())
615 .prop_map(|(base, byte_offset)| Self::FrameBaseAddress { base, byte_offset })
616 .boxed(),
617 ]
618 .boxed()
619 }
620 }
621
622 impl Arbitrary for DebugLocationExpression {
623 type Parameters = ();
624 type Strategy = BoxedStrategy<Self>;
625
626 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
627 let length = prop_oneof![
632 (0usize..=8).boxed(),
633 Just(127usize),
634 Just(128usize),
635 Just(255usize),
636 Just(256usize),
637 ];
638 length
639 .prop_flat_map(|len| {
640 proptest::collection::vec(any::<DebugLocationExpressionOp>(), len)
641 })
642 .prop_map(|operations| Self { operations })
643 .boxed()
644 }
645 }
646
647 impl Arbitrary for DebugVarLocation {
648 type Parameters = ();
649 type Strategy = BoxedStrategy<Self>;
650
651 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
652 prop_oneof![
655 any::<u8>().prop_map(Self::Stack).boxed(),
656 any::<u32>().prop_map(Self::Memory).boxed(),
657 any::<Felt>().prop_map(Self::Const).boxed(),
658 any::<i16>().prop_map(Self::Local).boxed(),
659 Just(Self::Unavailable),
660 (any::<DebugFrameBase>(), any::<i64>())
661 .prop_map(|(base, byte_offset)| Self::ResolvedFrameBase { base, byte_offset })
662 .boxed(),
663 any::<DebugLocationExpression>().prop_map(Self::Expression).boxed(),
664 ]
665 .boxed()
666 }
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use alloc::{string::ToString, vec::Vec};
673
674 use miden_core::serde::{Deserializable, Serializable, SliceReader};
675 use miden_debug_types::{ByteIndex, Uri};
676
677 use super::*;
678
679 #[test]
680 fn debug_var_info_display_simple() {
681 let var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
682 assert_eq!(var.to_string(), "var.x = stack[0]");
683 }
684
685 #[test]
686 fn debug_var_info_display_with_arg() {
687 let mut var = DebugVarInfo::new("param", DebugVarLocation::Stack(2));
688 var.set_arg_index(1);
689 assert_eq!(var.to_string(), "var.param[arg1] = stack[2]");
690 }
691
692 #[test]
693 fn debug_var_info_display_with_location() {
694 let mut var = DebugVarInfo::new("y", DebugVarLocation::Memory(100));
695 var.set_location(Location::new(
696 Uri::new("test.rs"),
697 ByteIndex::from(0u32),
698 ByteIndex::from(5u32),
699 ));
700 assert_eq!(var.to_string(), "var.y = mem[100] [test.rs@0..5]");
701 }
702
703 #[test]
704 fn debug_var_location_display() {
705 assert_eq!(DebugVarLocation::Stack(0).to_string(), "stack[0]");
706 assert_eq!(DebugVarLocation::Memory(256).to_string(), "mem[256]");
707 assert_eq!(DebugVarLocation::Const(Felt::new_unchecked(42)).to_string(), "const(42)");
708 assert_eq!(DebugVarLocation::Local(-3).to_string(), "FMP-3");
709 assert_eq!(
710 DebugVarLocation::ResolvedFrameBase {
711 base: DebugFrameBase::Local(-3),
712 byte_offset: 12,
713 }
714 .to_string(),
715 "frame-base(FMP-3)+12"
716 );
717 assert_eq!(DebugVarLocation::Unavailable.to_string(), "unavailable");
718 assert_eq!(
719 DebugVarLocation::Expression(
720 DebugLocationExpression::new(vec![
721 DebugLocationExpressionOp::FrameBaseAddress {
722 base: DebugFrameBase::Local(-2),
723 byte_offset: 4,
724 },
725 DebugLocationExpressionOp::AddUnsigned(8),
726 DebugLocationExpressionOp::DerefBytes,
727 ])
728 .unwrap(),
729 )
730 .to_string(),
731 "expr([FrameBaseAddress { base: Local(-2), byte_offset: 4 }, AddUnsigned(8), DerefBytes])"
732 );
733 }
734
735 #[test]
736 fn debug_var_location_serialization_round_trip() {
737 let locations = [
738 DebugVarLocation::Stack(7),
739 DebugVarLocation::Memory(0xdead_beef),
740 DebugVarLocation::Const(Felt::new_unchecked(999)),
741 DebugVarLocation::Local(-3),
742 DebugVarLocation::Unavailable,
743 DebugVarLocation::ResolvedFrameBase {
744 base: DebugFrameBase::Local(-3),
745 byte_offset: 28,
746 },
747 DebugVarLocation::ResolvedFrameBase {
748 base: DebugFrameBase::Memory(100),
749 byte_offset: -16,
750 },
751 DebugVarLocation::Expression(
752 DebugLocationExpression::new(vec![
753 DebugLocationExpressionOp::ReadStack(2),
754 DebugLocationExpressionOp::ConstI64(-4),
755 DebugLocationExpressionOp::Add,
756 DebugLocationExpressionOp::DerefBytes,
757 ])
758 .unwrap(),
759 ),
760 ];
761
762 for loc in &locations {
763 let mut bytes = Vec::new();
764 loc.write_into(&mut bytes);
765 let mut reader = SliceReader::new(&bytes);
766 let deser = DebugVarLocation::read_from(&mut reader).unwrap();
767 assert_eq!(&deser, loc);
768 }
769 }
770
771 #[test]
772 fn debug_location_expression_wire_encoding_is_stable() {
773 let expression = DebugLocationExpression::new(vec![
774 DebugLocationExpressionOp::ReadStack(0x2a),
775 DebugLocationExpressionOp::ReadMemory(0x1234_5678),
776 DebugLocationExpressionOp::ReadLocal(-2),
777 DebugLocationExpressionOp::ConstU64(0x0102_0304_0506_0708),
778 DebugLocationExpressionOp::ConstI64(-2),
779 DebugLocationExpressionOp::AddUnsigned(0x1112_1314_1516_1718),
780 DebugLocationExpressionOp::Add,
781 DebugLocationExpressionOp::Sub,
782 DebugLocationExpressionOp::DerefBytes,
783 DebugLocationExpressionOp::FrameBaseAddress {
784 base: DebugFrameBase::Local(-4),
785 byte_offset: 0x0102_0304_0506_0708,
786 },
787 DebugLocationExpressionOp::FrameBaseAddress {
788 base: DebugFrameBase::Memory(0xa1b2_c3d4),
789 byte_offset: -3,
790 },
791 ])
792 .unwrap();
793 let expected = vec![
794 0x17, 0x00, 0x2a, 0x01, 0x78, 0x56, 0x34, 0x12, 0x02, 0xfe, 0xff, 0x03, 0x08, 0x07,
795 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x04, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
796 0xff, 0x05, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x06, 0x07, 0x08, 0x09,
797 0x00, 0xfc, 0xff, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x09, 0x01, 0xd4,
798 0xc3, 0xb2, 0xa1, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
799 ];
800
801 let mut bytes = Vec::new();
802 expression.write_into(&mut bytes);
803 assert_eq!(bytes, expected);
804
805 let mut reader = SliceReader::new(&expected);
806 assert_eq!(DebugLocationExpression::read_from(&mut reader).unwrap(), expression);
807 }
808
809 #[test]
810 fn debug_var_location_min_serialized_size_matches_shortest_variant() {
811 let location = DebugVarLocation::Unavailable;
812 let min_serialized_size = DebugVarLocation::min_serialized_size();
813 let mut bytes = Vec::new();
814 location.write_into(&mut bytes);
815
816 assert_eq!(min_serialized_size, 1);
817 assert_eq!(bytes.len(), min_serialized_size);
818 }
819
820 #[test]
821 fn debug_location_expression_rejects_unknown_operation() {
822 let mut bytes = Vec::new();
823 bytes.write_usize(1);
824 bytes.write_u8(u8::MAX);
825
826 let mut reader = SliceReader::new(&bytes);
827 let err = DebugLocationExpression::read_from(&mut reader).unwrap_err();
828 let DeserializationError::InvalidValue(message) = err else {
829 panic!("expected InvalidValue error");
830 };
831 assert!(message.contains("invalid DebugLocationExpressionOp tag"));
832 }
833
834 #[test]
835 fn debug_location_expression_caps_operation_count_before_allocation() {
836 let count = MAX_DEBUG_LOCATION_EXPRESSION_OPS + 1;
837 let mut bytes = Vec::new();
838 bytes.write_usize(count);
839 bytes.resize(bytes.len() + count, 0);
840
841 let mut reader = SliceReader::new(&bytes);
842 let err = DebugLocationExpression::read_from(&mut reader).unwrap_err();
843 let DeserializationError::InvalidValue(message) = err else {
844 panic!("expected InvalidValue error");
845 };
846 assert!(message.contains("at most 256"));
847 }
848
849 #[test]
850 fn debug_location_expression_constructor_rejects_oversized_input() {
851 let operations =
852 vec![DebugLocationExpressionOp::Add; MAX_DEBUG_LOCATION_EXPRESSION_OPS + 1];
853 let error = DebugLocationExpression::new(operations).unwrap_err();
854
855 assert_eq!(error.operation_count(), MAX_DEBUG_LOCATION_EXPRESSION_OPS + 1);
856 }
857
858 #[test]
859 fn debug_var_info_set_value_location() {
860 let mut var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
861 var.set_value_location(DebugVarLocation::ResolvedFrameBase {
862 base: DebugFrameBase::Local(-2),
863 byte_offset: 12,
864 });
865 assert_eq!(
866 var.value_location(),
867 &DebugVarLocation::ResolvedFrameBase {
868 base: DebugFrameBase::Local(-2),
869 byte_offset: 12,
870 }
871 );
872 }
873
874 #[cfg(feature = "serde")]
875 #[test]
876 fn serde_round_trips_location_expressions() {
877 let expression = DebugLocationExpression::new(vec![
878 DebugLocationExpressionOp::ReadLocal(-2),
879 DebugLocationExpressionOp::DerefBytes,
880 ])
881 .unwrap();
882 let json = serde_json::to_string(&expression).unwrap();
883
884 assert_eq!(serde_json::from_str::<DebugLocationExpression>(&json).unwrap(), expression);
885 }
886
887 #[cfg(feature = "serde")]
888 #[test]
889 fn serde_rejects_oversized_location_expressions() {
890 let expression = DebugLocationExpression {
891 operations: vec![DebugLocationExpressionOp::Add; MAX_DEBUG_LOCATION_EXPRESSION_OPS + 1],
892 };
893 let json = serde_json::to_string(&expression).unwrap();
894 let error = serde_json::from_str::<DebugLocationExpression>(&json).unwrap_err();
895
896 assert!(error.to_string().contains("at most 256"));
897 }
898}