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(max_chars = 256, max_chars_allowance = 40)]
81 #[metastructure(skip_serialization = "empty", pii = "maybe")]
82 pub abs_path: Annotated<NativeImagePath>,
83
84 #[metastructure(skip_serialization = "null")]
86 pub lineno: Annotated<u64>,
87
88 #[metastructure(skip_serialization = "null")]
90 pub colno: Annotated<u64>,
91
92 #[metastructure(skip_serialization = "empty")]
97 pub platform: Annotated<String>,
98
99 #[metastructure(skip_serialization = "empty")]
101 pub pre_context: Annotated<Array<String>>,
102
103 #[metastructure(skip_serialization = "null")]
105 pub context_line: Annotated<String>,
106
107 #[metastructure(skip_serialization = "empty")]
109 pub post_context: Annotated<Array<String>>,
110
111 #[metastructure(skip_serialization = "null")]
117 pub in_app: Annotated<bool>,
118
119 #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
122 pub vars: Annotated<FrameVars>,
123
124 #[metastructure(omit_from_schema)]
126 #[metastructure(skip_serialization = "empty")]
127 pub data: Annotated<FrameData>,
128
129 #[metastructure(skip_serialization = "null")]
131 pub image_addr: Annotated<Addr>,
132
133 #[metastructure(skip_serialization = "null")]
140 pub instruction_addr: Annotated<Addr>,
141
142 #[metastructure(skip_serialization = "empty")]
152 pub addr_mode: Annotated<String>,
153
154 #[metastructure(skip_serialization = "empty")]
158 pub function_id: Annotated<Addr>,
159
160 #[metastructure(skip_serialization = "null")]
165 pub symbol_addr: Annotated<Addr>,
166
167 #[metastructure(max_chars = 128)]
169 #[metastructure(omit_from_schema)]
170 pub trust: Annotated<String>,
171
172 #[metastructure(max_chars = 128)]
174 #[metastructure(omit_from_schema)]
175 pub lang: Annotated<String>,
176
177 #[metastructure(skip_serialization = "null")]
186 pub stack_start: Annotated<bool>,
187
188 #[metastructure(skip_serialization = "null")]
190 pub lock: Annotated<LockReason>,
191
192 #[metastructure(additional_properties)]
194 pub other: Object<Value>,
195}
196
197#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
199pub struct FrameVars(#[metastructure(skip_serialization = "empty")] pub Object<Value>);
200
201#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
205pub struct FrameData {
206 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
208 sourcemap: Annotated<String>,
209 #[metastructure(max_chars = 256, max_chars_allowance = 20)]
211 orig_function: Annotated<String>,
212 #[metastructure(max_chars = 256, max_chars_allowance = 40)]
214 orig_filename: Annotated<String>,
215 orig_lineno: Annotated<u64>,
217 orig_colno: Annotated<u64>,
219 orig_in_app: Annotated<i64>,
228 #[metastructure(additional_properties)]
230 pub other: Object<Value>,
231}
232
233impl From<Object<Value>> for FrameVars {
234 fn from(value: Object<Value>) -> Self {
235 FrameVars(value)
236 }
237}
238
239impl FromValue for FrameVars {
240 fn from_value(mut value: Annotated<Value>) -> Annotated<FrameVars> {
241 value = value.map_value(|value| {
242 if let Value::Array(value) = value {
243 Value::Object(
244 value
245 .into_iter()
246 .enumerate()
247 .map(|(i, v)| (i.to_string(), v))
248 .collect(),
249 )
250 } else {
251 value
252 }
253 });
254
255 FromValue::from_value(value).map_value(FrameVars)
256 }
257}
258
259#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
328#[metastructure(process_func = "process_raw_stacktrace", value_type = "Stacktrace")]
329pub struct RawStacktrace {
330 #[metastructure(required = true, nonempty = true, skip_serialization = "empty")]
333 pub frames: Annotated<Array<Frame>>,
334
335 pub registers: Annotated<Object<RegVal>>,
340
341 #[metastructure(skip_serialization = "null")]
344 pub instruction_addr_adjustment: Annotated<InstructionAddrAdjustment>,
345
346 #[metastructure(max_chars = 128)]
348 pub lang: Annotated<String>,
349
350 pub snapshot: Annotated<bool>,
360
361 #[metastructure(additional_properties)]
363 pub other: Object<Value>,
364}
365
366#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, ProcessValue)]
377#[serde(rename_all = "snake_case")]
378pub enum InstructionAddrAdjustment {
379 Auto,
381
382 AllButFirst,
385
386 All,
389
390 None,
393
394 Unknown(String),
398}
399
400impl InstructionAddrAdjustment {
401 pub fn as_str(&self) -> &str {
403 match self {
404 InstructionAddrAdjustment::Auto => "auto",
405 InstructionAddrAdjustment::AllButFirst => "all_but_first",
406 InstructionAddrAdjustment::All => "all",
407 InstructionAddrAdjustment::None => "none",
408 InstructionAddrAdjustment::Unknown(s) => s,
409 }
410 }
411}
412
413impl FromStr for InstructionAddrAdjustment {
414 type Err = Infallible;
415
416 fn from_str(s: &str) -> Result<Self, Self::Err> {
417 match s {
418 "auto" => Ok(Self::Auto),
419 "all_but_first" => Ok(Self::AllButFirst),
420 "all" => Ok(Self::All),
421 "none" => Ok(Self::None),
422 s => Ok(Self::Unknown(s.to_string())),
423 }
424 }
425}
426
427impl fmt::Display for InstructionAddrAdjustment {
428 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429 f.write_str(self.as_str())
430 }
431}
432
433impl Default for InstructionAddrAdjustment {
434 fn default() -> Self {
435 Self::Auto
436 }
437}
438
439impl Empty for InstructionAddrAdjustment {
440 #[inline]
441 fn is_empty(&self) -> bool {
442 matches!(self, Self::Auto)
443 }
444}
445
446impl FromValue for InstructionAddrAdjustment {
447 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
448 match String::from_value(value) {
449 Annotated(Some(value), mut meta) => match value.parse() {
450 Ok(adjustment) => Annotated(Some(adjustment), meta),
451 Err(_) => {
452 meta.add_error(ErrorKind::InvalidData);
453 meta.set_original_value(Some(value));
454 Annotated(None, meta)
455 }
456 },
457 Annotated(None, meta) => Annotated(None, meta),
458 }
459 }
460}
461
462impl IntoValue for InstructionAddrAdjustment {
463 fn into_value(self) -> Value
464 where
465 Self: Sized,
466 {
467 Value::String(match self {
468 Self::Unknown(s) => s,
469 _ => self.as_str().to_owned(),
470 })
471 }
472
473 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
474 where
475 Self: Sized,
476 S: serde::Serializer,
477 {
478 serde::Serialize::serialize(self.as_str(), s)
479 }
480}
481
482#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
485#[metastructure(process_func = "process_stacktrace")]
486pub struct Stacktrace(pub RawStacktrace);
487
488impl Deref for Stacktrace {
489 type Target = RawStacktrace;
490
491 fn deref(&self) -> &RawStacktrace {
492 &self.0
493 }
494}
495
496impl DerefMut for Stacktrace {
497 fn deref_mut(&mut self) -> &mut RawStacktrace {
498 &mut self.0
499 }
500}
501
502impl From<RawStacktrace> for Stacktrace {
503 fn from(stacktrace: RawStacktrace) -> Stacktrace {
504 Stacktrace(stacktrace)
505 }
506}
507
508impl From<Stacktrace> for RawStacktrace {
509 fn from(stacktrace: Stacktrace) -> RawStacktrace {
510 stacktrace.0
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use crate::protocol::{LockReasonType, ThreadId};
517 use similar_asserts::assert_eq;
518
519 use super::*;
520
521 #[test]
522 fn test_frame_roundtrip() {
523 let json = r#"{
524 "function": "main@8",
525 "raw_function": "main",
526 "symbol": "_main@8",
527 "module": "app",
528 "package": "/my/app",
529 "filename": "myfile.rs",
530 "abs_path": "/path/to",
531 "lineno": 2,
532 "colno": 42,
533 "platform": "rust",
534 "pre_context": [
535 "fn main() {"
536 ],
537 "context_line": "unimplemented!()",
538 "post_context": [
539 "}"
540 ],
541 "in_app": true,
542 "vars": {
543 "variable": "value"
544 },
545 "data": {
546 "sourcemap": "http://example.com/invalid.map"
547 },
548 "image_addr": "0x400",
549 "instruction_addr": "0x404",
550 "addr_mode": "abs",
551 "symbol_addr": "0x404",
552 "trust": "69",
553 "lang": "rust",
554 "stack_start": true,
555 "lock": {
556 "type": 2,
557 "address": "0x07d7437b",
558 "package_name": "io.sentry.samples",
559 "class_name": "MainActivity",
560 "thread_id": 7
561 },
562 "other": "value"
563}"#;
564 let frame = Annotated::new(Frame {
565 function: Annotated::new("main@8".to_string()),
566 raw_function: Annotated::new("main".to_string()),
567 symbol: Annotated::new("_main@8".to_string()),
568 module: Annotated::new("app".to_string()),
569 package: Annotated::new("/my/app".to_string()),
570 filename: Annotated::new("myfile.rs".into()),
571 abs_path: Annotated::new("/path/to".into()),
572 lineno: Annotated::new(2),
573 colno: Annotated::new(42),
574 platform: Annotated::new("rust".to_string()),
575 pre_context: Annotated::new(vec![Annotated::new("fn main() {".to_string())]),
576 context_line: Annotated::new("unimplemented!()".to_string()),
577 post_context: Annotated::new(vec![Annotated::new("}".to_string())]),
578 in_app: Annotated::new(true),
579 vars: {
580 let mut vars = Object::new();
581 vars.insert(
582 "variable".to_string(),
583 Annotated::new(Value::String("value".to_string())),
584 );
585 Annotated::new(vars.into())
586 },
587 data: Annotated::new(FrameData {
588 sourcemap: Annotated::new("http://example.com/invalid.map".to_string()),
589 ..Default::default()
590 }),
591 image_addr: Annotated::new(Addr(0x400)),
592 instruction_addr: Annotated::new(Addr(0x404)),
593 addr_mode: Annotated::new("abs".into()),
594 function_id: Annotated::empty(),
595 symbol_addr: Annotated::new(Addr(0x404)),
596 trust: Annotated::new("69".into()),
597 lang: Annotated::new("rust".into()),
598 stack_start: Annotated::new(true),
599 lock: Annotated::new(LockReason {
600 ty: Annotated::new(LockReasonType::Waiting),
601 address: Annotated::new("0x07d7437b".to_string()),
602 package_name: Annotated::new("io.sentry.samples".to_string()),
603 class_name: Annotated::new("MainActivity".to_string()),
604 thread_id: Annotated::new(ThreadId::Int(7)),
605 other: Default::default(),
606 }),
607 other: {
608 let mut vars = Object::new();
609 vars.insert(
610 "other".to_string(),
611 Annotated::new(Value::String("value".to_string())),
612 );
613 vars
614 },
615 });
616
617 assert_eq!(frame, Annotated::from_json(json).unwrap());
618 assert_eq!(json, frame.to_json_pretty().unwrap());
619 }
620
621 #[test]
622 fn test_frame_default_values() {
623 let json = "{}";
624 let frame = Annotated::new(Frame::default());
625
626 assert_eq!(frame, Annotated::from_json(json).unwrap());
627 assert_eq!(json, frame.to_json_pretty().unwrap());
628 }
629
630 #[test]
631 fn test_stacktrace_roundtrip() {
632 let json = r#"{
633 "frames": [
634 {
635 "function": "foobar"
636 }
637 ],
638 "registers": {
639 "cspr": "0x20000000",
640 "lr": "0x18a31aadc",
641 "pc": "0x18a310ea4",
642 "sp": "0x16fd75060"
643 },
644 "instruction_addr_adjustment": "all_but_first",
645 "lang": "rust",
646 "snapshot": false,
647 "other": "value"
648}"#;
649 let stack = Annotated::new(RawStacktrace {
650 frames: Annotated::new(vec![Annotated::new(Frame {
651 function: Annotated::new("foobar".to_string()),
652 ..Default::default()
653 })]),
654 registers: {
655 let mut registers = Object::new();
656 registers.insert("cspr".to_string(), Annotated::new(RegVal(0x2000_0000)));
657 registers.insert("lr".to_string(), Annotated::new(RegVal(0x1_8a31_aadc)));
658 registers.insert("pc".to_string(), Annotated::new(RegVal(0x1_8a31_0ea4)));
659 registers.insert("sp".to_string(), Annotated::new(RegVal(0x1_6fd7_5060)));
660 Annotated::new(registers)
661 },
662 instruction_addr_adjustment: Annotated::new(InstructionAddrAdjustment::AllButFirst),
663 lang: Annotated::new("rust".into()),
664 snapshot: Annotated::new(false),
665 other: {
666 let mut other = Object::new();
667 other.insert(
668 "other".to_string(),
669 Annotated::new(Value::String("value".to_string())),
670 );
671 other
672 },
673 });
674
675 assert_eq!(stack, Annotated::from_json(json).unwrap());
676 assert_eq!(json, stack.to_json_pretty().unwrap());
677 }
678
679 #[test]
680 fn test_stacktrace_default_values() {
681 let json = r#"{
683 "frames": [
684 {}
685 ]
686}"#;
687
688 let stack = Annotated::new(RawStacktrace {
689 frames: Annotated::new(vec![Annotated::new(Frame::default())]),
690 ..Default::default()
691 });
692
693 assert_eq!(stack, Annotated::from_json(json).unwrap());
694 assert_eq!(json, stack.to_json_pretty().unwrap());
695 }
696
697 #[test]
698 fn test_frame_vars_null_preserved() {
699 let json = r#"{
700 "vars": {
701 "despacito": null
702 }
703}"#;
704 let frame = Annotated::new(Frame {
705 vars: Annotated::new({
706 let mut vars = Object::new();
707 vars.insert("despacito".to_string(), Annotated::empty());
708 vars.into()
709 }),
710 ..Default::default()
711 });
712
713 assert_eq!(Annotated::from_json(json).unwrap(), frame);
714 assert_eq!(json, frame.to_json_pretty().unwrap());
715 }
716
717 #[test]
718 fn test_frame_vars_empty_annotated_is_serialized() {
719 let output = r#"{
720 "vars": {
721 "despacito": null,
722 "despacito2": null
723 }
724}"#;
725 let frame = Annotated::new(Frame {
726 vars: Annotated::new({
727 let mut vars = Object::new();
728 vars.insert("despacito".to_string(), Annotated::empty());
729 vars.insert("despacito2".to_string(), Annotated::empty());
730 vars.into()
731 }),
732 ..Default::default()
733 });
734
735 assert_eq!(output, frame.to_json_pretty().unwrap());
736 }
737
738 #[test]
739 fn test_frame_empty_context_lines() {
740 let json = r#"{
741 "pre_context": [
742 ""
743 ],
744 "context_line": "",
745 "post_context": [
746 ""
747 ]
748}"#;
749
750 let frame = Annotated::new(Frame {
751 pre_context: Annotated::new(vec![Annotated::new("".to_string())]),
752 context_line: Annotated::new("".to_string()),
753 post_context: Annotated::new(vec![Annotated::new("".to_string())]),
754 ..Frame::default()
755 });
756
757 assert_eq!(frame, Annotated::from_json(json).unwrap());
758 assert_eq!(json, frame.to_json_pretty().unwrap());
759 }
760
761 #[test]
762 fn test_php_frame_vars() {
763 let input = r#"{
768 "vars": ["foo", "bar", "baz", null]
769}"#;
770
771 let output = r#"{
772 "vars": {
773 "0": "foo",
774 "1": "bar",
775 "2": "baz",
776 "3": null
777 }
778}"#;
779
780 let frame = Annotated::new(Frame {
781 vars: Annotated::new({
782 let mut vars = Object::new();
783 vars.insert("0".to_string(), Annotated::new("foo".to_string().into()));
784 vars.insert("1".to_string(), Annotated::new("bar".to_string().into()));
785 vars.insert("2".to_string(), Annotated::new("baz".to_string().into()));
786 vars.insert("3".to_string(), Annotated::empty());
787 vars.into()
788 }),
789 ..Default::default()
790 });
791
792 assert_eq!(frame, Annotated::from_json(input).unwrap());
793 assert_eq!(output, frame.to_json_pretty().unwrap());
794 }
795}