1use std::convert::Infallible;
2use std::fmt;
3use std::ops::{Deref, DerefMut};
4use std::str::FromStr;
5
6use relay_protocol::{
7 Annotated, Array, Empty, ErrorKind, FromValue, IntoValue, Object, SkipSerialization, Value,
8};
9use serde::{Deserialize, Serialize};
10
11use crate::processor::ProcessValue;
12use crate::protocol::{Addr, LockReason, NativeImagePath, RegVal};
13
14#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
19#[metastructure(process_func = "process_frame", value_type = "Frame")]
20pub struct Frame {
21 #[metastructure(max_chars = 512, max_chars_allowance = 20)]
26 #[metastructure(skip_serialization = "empty")]
27 pub function: Annotated<String>,
28
29 #[metastructure(max_chars = 512, max_chars_allowance = 20)]
45 #[metastructure(skip_serialization = "empty")]
46 pub raw_function: Annotated<String>,
47
48 #[metastructure(max_chars = 512)]
56 pub symbol: Annotated<String>,
57
58 #[metastructure(skip_serialization = "empty", pii = "maybe")]
63 pub module: Annotated<String>,
65
66 #[metastructure(skip_serialization = "empty")]
71 pub package: Annotated<String>,
73
74 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
76 #[metastructure(skip_serialization = "empty", pii = "maybe")]
77 pub filename: Annotated<NativeImagePath>,
78
79 #[metastructure(skip_serialization = "empty", pii = "maybe")]
87 pub file_revision: Annotated<String>,
88
89 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
91 #[metastructure(skip_serialization = "empty", pii = "maybe")]
92 pub abs_path: Annotated<NativeImagePath>,
93
94 #[metastructure(skip_serialization = "null")]
96 pub lineno: Annotated<u64>,
97
98 #[metastructure(skip_serialization = "null")]
100 pub colno: Annotated<u64>,
101
102 #[metastructure(skip_serialization = "empty")]
107 pub platform: Annotated<String>,
108
109 #[metastructure(skip_serialization = "empty")]
111 pub pre_context: Annotated<Array<String>>,
112
113 #[metastructure(skip_serialization = "null")]
115 pub context_line: Annotated<String>,
116
117 #[metastructure(skip_serialization = "null")]
119 pub parent_index: Annotated<i64>,
120
121 #[metastructure(skip_serialization = "null")]
123 pub sample_count: Annotated<u64>,
124
125 #[metastructure(skip_serialization = "empty")]
127 pub post_context: Annotated<Array<String>>,
128
129 #[metastructure(skip_serialization = "null")]
135 pub in_app: Annotated<bool>,
136
137 #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
140 pub vars: Annotated<FrameVars>,
141
142 #[metastructure(omit_from_schema)]
144 #[metastructure(skip_serialization = "empty")]
145 pub data: Annotated<FrameData>,
146
147 #[metastructure(skip_serialization = "null")]
149 pub image_addr: Annotated<Addr>,
150
151 #[metastructure(skip_serialization = "null")]
158 pub instruction_addr: Annotated<Addr>,
159
160 #[metastructure(skip_serialization = "empty")]
170 pub addr_mode: Annotated<String>,
171
172 #[metastructure(skip_serialization = "empty")]
176 pub function_id: Annotated<Addr>,
177
178 #[metastructure(skip_serialization = "null")]
183 pub symbol_addr: Annotated<Addr>,
184
185 #[metastructure(max_chars = 128)]
187 #[metastructure(omit_from_schema)]
188 pub trust: Annotated<String>,
189
190 #[metastructure(max_chars = 128)]
192 #[metastructure(omit_from_schema)]
193 pub lang: Annotated<String>,
194
195 #[metastructure(skip_serialization = "null")]
204 pub stack_start: Annotated<bool>,
205
206 #[metastructure(skip_serialization = "null")]
208 pub lock: Annotated<LockReason>,
209
210 #[metastructure(additional_properties)]
212 pub other: Object<Value>,
213}
214
215#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
217pub struct FrameVars(#[metastructure(skip_serialization = "empty")] pub Object<Value>);
218
219#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
223pub struct FrameData {
224 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
226 sourcemap: Annotated<String>,
227 #[metastructure(max_chars = 256, max_chars_allowance = 20)]
229 orig_function: Annotated<String>,
230 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
232 orig_filename: Annotated<String>,
233 orig_lineno: Annotated<u64>,
235 orig_colno: Annotated<u64>,
237 orig_in_app: Annotated<i64>,
246 #[metastructure(additional_properties)]
248 pub other: Object<Value>,
249}
250
251impl From<Object<Value>> for FrameVars {
252 fn from(value: Object<Value>) -> Self {
253 FrameVars(value)
254 }
255}
256
257impl FromValue for FrameVars {
258 fn from_value(mut value: Annotated<Value>) -> Annotated<FrameVars> {
259 value = value.map_value(|value| {
260 if let Value::Array(value) = value {
261 Value::Object(
262 value
263 .into_iter()
264 .enumerate()
265 .map(|(i, v)| (i.to_string(), v))
266 .collect(),
267 )
268 } else {
269 value
270 }
271 });
272
273 FromValue::from_value(value).map_value(FrameVars)
274 }
275}
276
277#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
346#[metastructure(process_func = "process_raw_stacktrace", value_type = "Stacktrace")]
347pub struct RawStacktrace {
348 #[metastructure(required = true, nonempty = true, skip_serialization = "empty")]
351 pub frames: Annotated<Array<Frame>>,
352
353 pub registers: Annotated<Object<RegVal>>,
358
359 #[metastructure(skip_serialization = "null")]
362 pub instruction_addr_adjustment: Annotated<InstructionAddrAdjustment>,
363
364 #[metastructure(max_chars = 128)]
366 pub lang: Annotated<String>,
367
368 pub snapshot: Annotated<bool>,
378
379 #[metastructure(additional_properties)]
381 pub other: Object<Value>,
382}
383
384impl RawStacktrace {
385 pub const MAX_OLDEST_FRAMES: usize = 50;
387 pub const MAX_NEWEST_FRAMES: usize = 200;
389 pub const MAX_FRAMES: usize = Self::MAX_OLDEST_FRAMES + Self::MAX_NEWEST_FRAMES;
391}
392
393#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, ProcessValue)]
404#[serde(rename_all = "snake_case")]
405#[derive(Default)]
406pub enum InstructionAddrAdjustment {
407 #[default]
409 Auto,
410
411 AllButFirst,
414
415 All,
418
419 None,
422
423 Unknown(String),
427}
428
429impl InstructionAddrAdjustment {
430 pub fn as_str(&self) -> &str {
432 match self {
433 InstructionAddrAdjustment::Auto => "auto",
434 InstructionAddrAdjustment::AllButFirst => "all_but_first",
435 InstructionAddrAdjustment::All => "all",
436 InstructionAddrAdjustment::None => "none",
437 InstructionAddrAdjustment::Unknown(s) => s,
438 }
439 }
440}
441
442impl FromStr for InstructionAddrAdjustment {
443 type Err = Infallible;
444
445 fn from_str(s: &str) -> Result<Self, Self::Err> {
446 match s {
447 "auto" => Ok(Self::Auto),
448 "all_but_first" => Ok(Self::AllButFirst),
449 "all" => Ok(Self::All),
450 "none" => Ok(Self::None),
451 s => Ok(Self::Unknown(s.to_owned())),
452 }
453 }
454}
455
456impl fmt::Display for InstructionAddrAdjustment {
457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458 f.write_str(self.as_str())
459 }
460}
461
462impl Empty for InstructionAddrAdjustment {
463 #[inline]
464 fn is_empty(&self) -> bool {
465 matches!(self, Self::Auto)
466 }
467}
468
469impl FromValue for InstructionAddrAdjustment {
470 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
471 match String::from_value(value) {
472 Annotated(Some(value), mut meta) => match value.parse() {
473 Ok(adjustment) => Annotated(Some(adjustment), meta),
474 Err(_) => {
475 meta.add_error(ErrorKind::InvalidData);
476 meta.set_original_value(Some(value));
477 Annotated(None, meta)
478 }
479 },
480 Annotated(None, meta) => Annotated(None, meta),
481 }
482 }
483}
484
485impl IntoValue for InstructionAddrAdjustment {
486 fn into_value(self) -> Value
487 where
488 Self: Sized,
489 {
490 Value::String(match self {
491 Self::Unknown(s) => s,
492 _ => self.as_str().to_owned(),
493 })
494 }
495
496 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
497 where
498 Self: Sized,
499 S: serde::Serializer,
500 {
501 serde::Serialize::serialize(self.as_str(), s)
502 }
503}
504
505#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
508#[metastructure(process_func = "process_stacktrace")]
509pub struct Stacktrace(pub RawStacktrace);
510
511impl Deref for Stacktrace {
512 type Target = RawStacktrace;
513
514 fn deref(&self) -> &RawStacktrace {
515 &self.0
516 }
517}
518
519impl DerefMut for Stacktrace {
520 fn deref_mut(&mut self) -> &mut RawStacktrace {
521 &mut self.0
522 }
523}
524
525impl From<RawStacktrace> for Stacktrace {
526 fn from(stacktrace: RawStacktrace) -> Stacktrace {
527 Stacktrace(stacktrace)
528 }
529}
530
531impl From<Stacktrace> for RawStacktrace {
532 fn from(stacktrace: Stacktrace) -> RawStacktrace {
533 stacktrace.0
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use crate::protocol::{LockReasonType, ThreadId};
540 use similar_asserts::assert_eq;
541
542 use super::*;
543
544 #[test]
545 fn test_frame_roundtrip() {
546 let json = r#"{
547 "function": "main@8",
548 "raw_function": "main",
549 "symbol": "_main@8",
550 "module": "app",
551 "package": "/my/app",
552 "filename": "myfile.rs",
553 "file_revision": "12345",
554 "abs_path": "/path/to",
555 "lineno": 2,
556 "colno": 42,
557 "platform": "rust",
558 "pre_context": [
559 "fn main() {"
560 ],
561 "context_line": "unimplemented!()",
562 "post_context": [
563 "}"
564 ],
565 "in_app": true,
566 "vars": {
567 "variable": "value"
568 },
569 "data": {
570 "sourcemap": "http://example.com/invalid.map"
571 },
572 "image_addr": "0x400",
573 "instruction_addr": "0x404",
574 "addr_mode": "abs",
575 "symbol_addr": "0x404",
576 "trust": "69",
577 "lang": "rust",
578 "stack_start": true,
579 "lock": {
580 "type": 2,
581 "address": "0x07d7437b",
582 "package_name": "io.sentry.samples",
583 "class_name": "MainActivity",
584 "thread_id": 7
585 },
586 "other": "value"
587}"#;
588 let frame = Annotated::new(Frame {
589 function: Annotated::new("main@8".to_owned()),
590 raw_function: Annotated::new("main".to_owned()),
591 symbol: Annotated::new("_main@8".to_owned()),
592 module: Annotated::new("app".to_owned()),
593 package: Annotated::new("/my/app".to_owned()),
594 filename: Annotated::new("myfile.rs".into()),
595 file_revision: Annotated::new("12345".into()),
596 abs_path: Annotated::new("/path/to".into()),
597 lineno: Annotated::new(2),
598 colno: Annotated::new(42),
599 platform: Annotated::new("rust".to_owned()),
600 pre_context: Annotated::new(vec![Annotated::new("fn main() {".to_owned())]),
601 context_line: Annotated::new("unimplemented!()".to_owned()),
602 post_context: Annotated::new(vec![Annotated::new("}".to_owned())]),
603 in_app: Annotated::new(true),
604 vars: {
605 let mut vars = Object::new();
606 vars.insert(
607 "variable".to_owned(),
608 Annotated::new(Value::String("value".to_owned())),
609 );
610 Annotated::new(vars.into())
611 },
612 data: Annotated::new(FrameData {
613 sourcemap: Annotated::new("http://example.com/invalid.map".to_owned()),
614 ..Default::default()
615 }),
616 image_addr: Annotated::new(Addr(0x400)),
617 instruction_addr: Annotated::new(Addr(0x404)),
618 addr_mode: Annotated::new("abs".into()),
619 function_id: Annotated::empty(),
620 symbol_addr: Annotated::new(Addr(0x404)),
621 trust: Annotated::new("69".into()),
622 lang: Annotated::new("rust".into()),
623 stack_start: Annotated::new(true),
624 lock: Annotated::new(LockReason {
625 ty: Annotated::new(LockReasonType::Waiting),
626 address: Annotated::new("0x07d7437b".to_owned()),
627 package_name: Annotated::new("io.sentry.samples".to_owned()),
628 class_name: Annotated::new("MainActivity".to_owned()),
629 thread_id: Annotated::new(ThreadId::Int(7)),
630 other: Default::default(),
631 }),
632 parent_index: Annotated::empty(),
633 sample_count: Annotated::empty(),
634 other: {
635 let mut vars = Object::new();
636 vars.insert(
637 "other".to_owned(),
638 Annotated::new(Value::String("value".to_owned())),
639 );
640 vars
641 },
642 });
643
644 assert_eq!(frame, Annotated::from_json(json).unwrap());
645 assert_eq!(json, frame.to_json_pretty().unwrap());
646 }
647
648 #[test]
649 fn test_frame_default_values() {
650 let json = "{}";
651 let frame = Annotated::new(Frame::default());
652
653 assert_eq!(frame, Annotated::from_json(json).unwrap());
654 assert_eq!(json, frame.to_json_pretty().unwrap());
655 }
656
657 #[test]
658 fn test_stacktrace_roundtrip() {
659 let json = r#"{
660 "frames": [
661 {
662 "function": "foobar"
663 }
664 ],
665 "registers": {
666 "cspr": "0x20000000",
667 "lr": "0x18a31aadc",
668 "pc": "0x18a310ea4",
669 "sp": "0x16fd75060"
670 },
671 "instruction_addr_adjustment": "all_but_first",
672 "lang": "rust",
673 "snapshot": false,
674 "other": "value"
675}"#;
676 let stack = Annotated::new(RawStacktrace {
677 frames: Annotated::new(vec![Annotated::new(Frame {
678 function: Annotated::new("foobar".to_owned()),
679 ..Default::default()
680 })]),
681 registers: {
682 let mut registers = Object::new();
683 registers.insert("cspr".to_owned(), Annotated::new(RegVal(0x2000_0000)));
684 registers.insert("lr".to_owned(), Annotated::new(RegVal(0x1_8a31_aadc)));
685 registers.insert("pc".to_owned(), Annotated::new(RegVal(0x1_8a31_0ea4)));
686 registers.insert("sp".to_owned(), Annotated::new(RegVal(0x1_6fd7_5060)));
687 Annotated::new(registers)
688 },
689 instruction_addr_adjustment: Annotated::new(InstructionAddrAdjustment::AllButFirst),
690 lang: Annotated::new("rust".into()),
691 snapshot: Annotated::new(false),
692 other: {
693 let mut other = Object::new();
694 other.insert(
695 "other".to_owned(),
696 Annotated::new(Value::String("value".to_owned())),
697 );
698 other
699 },
700 });
701
702 assert_eq!(stack, Annotated::from_json(json).unwrap());
703 assert_eq!(json, stack.to_json_pretty().unwrap());
704 }
705
706 #[test]
707 fn test_stacktrace_default_values() {
708 let json = r#"{
710 "frames": [
711 {}
712 ]
713}"#;
714
715 let stack = Annotated::new(RawStacktrace {
716 frames: Annotated::new(vec![Annotated::new(Frame::default())]),
717 ..Default::default()
718 });
719
720 assert_eq!(stack, Annotated::from_json(json).unwrap());
721 assert_eq!(json, stack.to_json_pretty().unwrap());
722 }
723
724 #[test]
725 fn test_frame_vars_null_preserved() {
726 let json = r#"{
727 "vars": {
728 "despacito": null
729 }
730}"#;
731 let frame = Annotated::new(Frame {
732 vars: Annotated::new({
733 let mut vars = Object::new();
734 vars.insert("despacito".to_owned(), Annotated::empty());
735 vars.into()
736 }),
737 ..Default::default()
738 });
739
740 assert_eq!(Annotated::from_json(json).unwrap(), frame);
741 assert_eq!(json, frame.to_json_pretty().unwrap());
742 }
743
744 #[test]
745 fn test_frame_vars_empty_annotated_is_serialized() {
746 let output = r#"{
747 "vars": {
748 "despacito": null,
749 "despacito2": null
750 }
751}"#;
752 let frame = Annotated::new(Frame {
753 vars: Annotated::new({
754 let mut vars = Object::new();
755 vars.insert("despacito".to_owned(), Annotated::empty());
756 vars.insert("despacito2".to_owned(), Annotated::empty());
757 vars.into()
758 }),
759 ..Default::default()
760 });
761
762 assert_eq!(output, frame.to_json_pretty().unwrap());
763 }
764
765 #[test]
766 fn test_frame_empty_context_lines() {
767 let json = r#"{
768 "pre_context": [
769 ""
770 ],
771 "context_line": "",
772 "post_context": [
773 ""
774 ]
775}"#;
776
777 let frame = Annotated::new(Frame {
778 pre_context: Annotated::new(vec![Annotated::new("".to_owned())]),
779 context_line: Annotated::new("".to_owned()),
780 post_context: Annotated::new(vec![Annotated::new("".to_owned())]),
781 ..Frame::default()
782 });
783
784 assert_eq!(frame, Annotated::from_json(json).unwrap());
785 assert_eq!(json, frame.to_json_pretty().unwrap());
786 }
787
788 #[test]
789 fn test_php_frame_vars() {
790 let input = r#"{
795 "vars": ["foo", "bar", "baz", null]
796}"#;
797
798 let output = r#"{
799 "vars": {
800 "0": "foo",
801 "1": "bar",
802 "2": "baz",
803 "3": null
804 }
805}"#;
806
807 let frame = Annotated::new(Frame {
808 vars: Annotated::new({
809 let mut vars = Object::new();
810 vars.insert("0".to_owned(), Annotated::new("foo".to_owned().into()));
811 vars.insert("1".to_owned(), Annotated::new("bar".to_owned().into()));
812 vars.insert("2".to_owned(), Annotated::new("baz".to_owned().into()));
813 vars.insert("3".to_owned(), Annotated::empty());
814 vars.into()
815 }),
816 ..Default::default()
817 });
818
819 assert_eq!(frame, Annotated::from_json(input).unwrap());
820 assert_eq!(output, frame.to_json_pretty().unwrap());
821 }
822}