Skip to main content

relay_event_schema/protocol/
stacktrace.rs

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/// Holds information about a single stacktrace frame.
15///
16/// Each object should contain **at least** a `filename`, `function` or `instruction_addr`
17/// attribute. All values are optional, but recommended.
18#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
19#[metastructure(process_func = "process_frame", value_type = "Frame")]
20pub struct Frame {
21    /// Name of the frame's function. This might include the name of a class.
22    ///
23    /// This function name may be shortened or demangled. If not, Sentry will demangle and shorten
24    /// it for some platforms. The original function name will be stored in `raw_function`.
25    #[metastructure(max_chars = 512, max_chars_allowance = 20)]
26    #[metastructure(skip_serialization = "empty")]
27    pub function: Annotated<String>,
28
29    /// A raw (but potentially truncated) function value.
30    ///
31    /// The original function name, if the function name is shortened or demangled. Sentry shows the
32    /// raw function when clicking on the shortened one in the UI.
33    ///
34    /// If this has the same value as `function` it's best to be omitted.  This exists because on
35    /// many platforms the function itself contains additional information like overload specifies
36    /// or a lot of generics which can make it exceed the maximum limit we provide for the field.
37    /// In those cases then we cannot reliably trim down the function any more at a later point
38    /// because the more valuable information has been removed.
39    ///
40    /// The logic to be applied is that an intelligently trimmed function name should be stored in
41    /// `function` and the value before trimming is stored in this field instead.  However also this
42    /// field will be capped at 512 characters at the moment which often means that not the entire
43    /// original value can be stored.
44    #[metastructure(max_chars = 512, max_chars_allowance = 20)]
45    #[metastructure(skip_serialization = "empty")]
46    pub raw_function: Annotated<String>,
47
48    /// Potentially mangled name of the symbol as it appears in an executable.
49    ///
50    /// This is different from a function name by generally being the mangled
51    /// name that appears natively in the binary.  This is relevant for languages
52    /// like Swift, C++ or Rust.
53    // XXX(markus): How is this different from just storing the mangled function name in
54    // `function`?
55    #[metastructure(max_chars = 512)]
56    pub symbol: Annotated<String>,
57
58    /// Name of the module the frame is contained in.
59    ///
60    /// Note that this might also include a class name if that is something the
61    /// language natively considers to be part of the stack (for instance in Java).
62    #[metastructure(skip_serialization = "empty", pii = "maybe")]
63    // TODO: Cap? This can be a FS path or a dotted path
64    pub module: Annotated<String>,
65
66    /// Name of the package that contains the frame.
67    ///
68    /// For instance this can be a dylib for native languages, the name of the jar
69    /// or .NET assembly.
70    #[metastructure(skip_serialization = "empty")]
71    // TODO: Cap? This can be a FS path or a dotted path
72    pub package: Annotated<String>,
73
74    /// The source file name (basename only).
75    #[metastructure(max_chars = 256, max_chars_allowance = 40)]
76    #[metastructure(skip_serialization = "empty", pii = "maybe")]
77    pub filename: Annotated<NativeImagePath>,
78
79    /// The revision of the frame's file on a source server.
80    ///
81    /// This is only relevant for frames symbolicated with source
82    /// server information available, cf. <https://github.com/getsentry/symbolicator/pull/2071>.
83    ///
84    /// The meaning of this field depends on the version control system
85    /// used by the source server.
86    #[metastructure(skip_serialization = "empty", pii = "maybe")]
87    pub file_revision: Annotated<String>,
88
89    /// Absolute path to the source file.
90    #[metastructure(max_chars = 256, max_chars_allowance = 40)]
91    #[metastructure(skip_serialization = "empty", pii = "maybe")]
92    pub abs_path: Annotated<NativeImagePath>,
93
94    /// Line number within the source file, starting at 1.
95    #[metastructure(skip_serialization = "null")]
96    pub lineno: Annotated<u64>,
97
98    /// Column number within the source file, starting at 1.
99    #[metastructure(skip_serialization = "null")]
100    pub colno: Annotated<u64>,
101
102    /// Which platform this frame is from.
103    ///
104    /// This can override the platform for a single frame. Otherwise, the platform of the event is
105    /// assumed. This can be used for multi-platform stack traces, such as in React Native.
106    #[metastructure(skip_serialization = "empty")]
107    pub platform: Annotated<String>,
108
109    /// Source code leading up to `lineno`.
110    #[metastructure(skip_serialization = "empty")]
111    pub pre_context: Annotated<Array<String>>,
112
113    /// Source code of the current line (`lineno`).
114    #[metastructure(skip_serialization = "null")]
115    pub context_line: Annotated<String>,
116
117    /// The index of the parent frame, used for flamegraphs provided by iOS.
118    #[metastructure(skip_serialization = "null")]
119    pub parent_index: Annotated<i64>,
120
121    /// The number of times this frame was sampled, used for flamegraphs provided by iOS.
122    #[metastructure(skip_serialization = "null")]
123    pub sample_count: Annotated<u64>,
124
125    /// Source code of the lines after `lineno`.
126    #[metastructure(skip_serialization = "empty")]
127    pub post_context: Annotated<Array<String>>,
128
129    /// Override whether this frame should be considered part of application code, or part of
130    /// libraries/frameworks/dependencies.
131    ///
132    /// Setting this attribute to `false` causes the frame to be hidden/collapsed by default and
133    /// mostly ignored during issue grouping.
134    #[metastructure(skip_serialization = "null")]
135    pub in_app: Annotated<bool>,
136
137    /// Mapping of local variables and expression names that were available in this frame.
138    // XXX: Probably want to trim per-var => new bag size?
139    #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
140    pub vars: Annotated<FrameVars>,
141
142    /// Auxiliary information about the frame that is platform specific.
143    #[metastructure(omit_from_schema)]
144    #[metastructure(skip_serialization = "empty")]
145    pub data: Annotated<FrameData>,
146
147    /// (C/C++/Native) Start address of the containing code module (image).
148    #[metastructure(skip_serialization = "null")]
149    pub image_addr: Annotated<Addr>,
150
151    /// (C/C++/Native) An optional instruction address for symbolication.
152    ///
153    /// This should be a string with a hexadecimal number that includes a 0x prefix.
154    /// If this is set and a known image is defined in the
155    /// [Debug Meta Interface]({%- link _documentation/development/sdk-dev/event-payloads/debugmeta.md -%}),
156    /// then symbolication can take place.
157    #[metastructure(skip_serialization = "null")]
158    pub instruction_addr: Annotated<Addr>,
159
160    /// Defines the addressing mode for addresses.
161    ///
162    /// This can be:
163    /// - `"abs"` (the default): `instruction_addr` is absolute.
164    /// - `"rel:$idx"`: `instruction_addr` is relative to the `debug_meta.image` identified by its index in the list.
165    /// - `"rel:$uuid"`: `instruction_addr` is relative to the `debug_meta.image` identified by its `debug_id`.
166    ///
167    /// If one of the `"rel:XXX"` variants is given together with `function_id`, the `instruction_addr` is relative
168    /// to the uniquely identified function in the references `debug_meta.image`.
169    #[metastructure(skip_serialization = "empty")]
170    pub addr_mode: Annotated<String>,
171
172    /// (.NET) The function id / index that uniquely identifies a function inside a module.
173    ///
174    /// This is the `MetadataToken` of a .NET `MethodBase`.
175    #[metastructure(skip_serialization = "empty")]
176    pub function_id: Annotated<Addr>,
177
178    /// (C/C++/Native) Start address of the frame's function.
179    ///
180    /// We use the instruction address for symbolication, but this can be used to calculate
181    /// an instruction offset automatically.
182    #[metastructure(skip_serialization = "null")]
183    pub symbol_addr: Annotated<Addr>,
184
185    /// (C/C++/Native) Used for native crashes to indicate how much we can "trust" the instruction_addr
186    #[metastructure(max_chars = 128)]
187    #[metastructure(omit_from_schema)]
188    pub trust: Annotated<String>,
189
190    /// The language of the frame if it overrides the stacktrace language.
191    #[metastructure(max_chars = 128)]
192    #[metastructure(omit_from_schema)]
193    pub lang: Annotated<String>,
194
195    /// Marks this frame as the bottom of a chained stack trace.
196    ///
197    /// Stack traces from asynchronous code consist of several sub traces that are chained together
198    /// into one large list. This flag indicates the root function of a chained stack trace.
199    /// Depending on the runtime and thread, this is either the `main` function or a thread base
200    /// stub.
201    ///
202    /// This field should only be specified when true.
203    #[metastructure(skip_serialization = "null")]
204    pub stack_start: Annotated<bool>,
205
206    /// A possible lock (java monitor object) held by this frame.
207    #[metastructure(skip_serialization = "null")]
208    pub lock: Annotated<LockReason>,
209
210    /// Additional arbitrary fields for forwards compatibility.
211    #[metastructure(additional_properties)]
212    pub other: Object<Value>,
213}
214
215/// Frame local variables.
216#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
217pub struct FrameVars(#[metastructure(skip_serialization = "empty")] pub Object<Value>);
218
219/// Additional frame data information.
220///
221/// This value is set by the server and should not be set by the SDK.
222#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
223pub struct FrameData {
224    /// A reference to the sourcemap used.
225    #[metastructure(max_chars = 256, max_chars_allowance = 40)]
226    sourcemap: Annotated<String>,
227    /// The original function name before it was resolved.
228    #[metastructure(max_chars = 256, max_chars_allowance = 20)]
229    orig_function: Annotated<String>,
230    /// The original minified filename.
231    #[metastructure(max_chars = 256, max_chars_allowance = 40)]
232    orig_filename: Annotated<String>,
233    /// The original line number.
234    orig_lineno: Annotated<u64>,
235    /// The original column number.
236    orig_colno: Annotated<u64>,
237    /// The original value of the in_app flag before grouping enhancers ran.
238    ///
239    /// Because we need to handle more cases the following values are used:
240    ///
241    /// - missing / `null`: information not available
242    /// - `-1`: in_app was set to `null`
243    /// - `0`: in_app was set to `false`
244    /// - `1`: in_app was set to `true`
245    orig_in_app: Annotated<i64>,
246    /// Additional keys not handled by this protocol.
247    #[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/// A stack trace of a single thread.
278///
279/// A stack trace contains a list of frames, each with various bits (most optional) describing the
280/// context of that frame. Frames should be sorted from oldest to newest.
281///
282/// For the given example program written in Python:
283///
284/// ```python
285/// def foo():
286///     my_var = 'foo'
287///     raise ValueError()
288///
289/// def main():
290///     foo()
291/// ```
292///
293/// A minimalistic stack trace for the above program in the correct order:
294///
295/// ```json
296/// {
297///   "frames": [
298///     {"function": "main"},
299///     {"function": "foo"}
300///   ]
301/// }
302/// ```
303///
304/// The top frame fully symbolicated with five lines of source context:
305///
306/// ```json
307/// {
308///   "frames": [{
309///     "in_app": true,
310///     "function": "myfunction",
311///     "abs_path": "/real/file/name.py",
312///     "filename": "file/name.py",
313///     "lineno": 3,
314///     "vars": {
315///       "my_var": "'value'"
316///     },
317///     "pre_context": [
318///       "def foo():",
319///       "  my_var = 'foo'",
320///     ],
321///     "context_line": "  raise ValueError()",
322///     "post_context": [
323///       "",
324///       "def main():"
325///     ],
326///   }]
327/// }
328/// ```
329///
330/// A minimal native stack trace with register values. Note that the `package` event attribute must
331/// be "native" for these frames to be symbolicated.
332///
333/// ```json
334/// {
335///   "frames": [
336///     {"instruction_addr": "0x7fff5bf3456c"},
337///     {"instruction_addr": "0x7fff5bf346c0"},
338///   ],
339///   "registers": {
340///     "rip": "0x00007ff6eef54be2",
341///     "rsp": "0x0000003b710cd9e0"
342///   }
343/// }
344/// ```
345#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
346#[metastructure(process_func = "process_raw_stacktrace", value_type = "Stacktrace")]
347pub struct RawStacktrace {
348    /// Required. A non-empty list of stack frames. The list is ordered from caller to callee, or
349    /// oldest to youngest. The last frame is the one creating the exception.
350    #[metastructure(required = true, nonempty = true, skip_serialization = "empty")]
351    pub frames: Annotated<Array<Frame>>,
352
353    /// Register values of the thread (top frame).
354    ///
355    /// A map of register names and their values. The values should contain the actual register
356    /// values of the thread, thus mapping to the last frame in the list.
357    pub registers: Annotated<Object<RegVal>>,
358
359    /// Optional. A flag that indicates if, and how, `instruction_addr` values need to be adjusted
360    /// before they are symbolicated.
361    #[metastructure(skip_serialization = "null")]
362    pub instruction_addr_adjustment: Annotated<InstructionAddrAdjustment>,
363
364    /// The language of the stacktrace.
365    #[metastructure(max_chars = 128)]
366    pub lang: Annotated<String>,
367
368    /// Indicates that this stack trace is a snapshot triggered by an external signal.
369    ///
370    /// If this field is `false`, then the stack trace points to the code that caused this stack
371    /// trace to be created. This can be the location of a raised exception, as well as an exception
372    /// or signal handler.
373    ///
374    /// If this field is `true`, then the stack trace was captured as part of creating an unrelated
375    /// event. For example, a thread other than the crashing thread, or a stack trace computed as a
376    /// result of an external kill signal.
377    pub snapshot: Annotated<bool>,
378
379    /// Additional arbitrary fields for forwards compatibility.
380    #[metastructure(additional_properties)]
381    pub other: Object<Value>,
382}
383
384impl RawStacktrace {
385    /// Number of oldest frames retained when a stack exceeds the frame limit.
386    pub const MAX_OLDEST_FRAMES: usize = 50;
387    /// Number of newest frames retained when a stack exceeds the frame limit.
388    pub const MAX_NEWEST_FRAMES: usize = 200;
389    /// Maximum number of frames retained during normalization.
390    pub const MAX_FRAMES: usize = Self::MAX_OLDEST_FRAMES + Self::MAX_NEWEST_FRAMES;
391}
392
393/// Controls the mechanism by which the `instruction_addr` of a [`Stacktrace`] [`Frame`] is adjusted.
394///
395/// The adjustment tries to transform *return addresses* to *call addresses* for symbolication.
396/// Typically, this adjustment needs to be done for all frames but the first, as the first frame is
397/// usually taken directly from the cpu context of a hardware exception or a suspended thread and
398/// the stack trace is created from that.
399///
400/// When the stack walking implementation truncates frames from the top, `"all"` frames should be
401/// adjusted. In case the stack walking implementation already does the adjustment when producing
402/// stack frames, `"none"` should be used here.
403#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, ProcessValue)]
404#[serde(rename_all = "snake_case")]
405#[derive(Default)]
406pub enum InstructionAddrAdjustment {
407    /// The default. Applies a heuristic based on other event / exception attributes.
408    #[default]
409    Auto,
410
411    /// All but the first frame needs to be adjusted. The first frame's address is not a *return address*,
412    /// but points directly to the faulty instruction.
413    AllButFirst,
414
415    /// All frames should be adjusted, for example because the stack walking implementation truncated
416    /// frames from the top of the stack, and all remaining frames' addresses are *return addresses*.
417    All,
418
419    /// The stack walking implementation already provides correct addresses and no adjustment should
420    /// be performed when symbolicating.
421    None,
422
423    /// Any other unknown adjustment strategy.
424    ///
425    /// This exists to ensure forward compatibility.
426    Unknown(String),
427}
428
429impl InstructionAddrAdjustment {
430    /// Returns the string representation of this adjustment.
431    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// NOTE: This is not a doc comment because otherwise it will show up in public docs.
506// Newtype to distinguish `raw_stacktrace` attributes from the rest.
507#[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        // This needs an empty frame because "frames" is required
709        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        // Buggy PHP SDKs send us this stuff
791        //
792        // Port of https://github.com/getsentry/sentry/commit/73d9a061dcac3ab8c318a09735601a12e81085dd
793
794        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}