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    /// Absolute path to the source file.
80    #[metastructure(max_chars = 256, max_chars_allowance = 40)]
81    #[metastructure(skip_serialization = "empty", pii = "maybe")]
82    pub abs_path: Annotated<NativeImagePath>,
83
84    /// Line number within the source file, starting at 1.
85    #[metastructure(skip_serialization = "null")]
86    pub lineno: Annotated<u64>,
87
88    /// Column number within the source file, starting at 1.
89    #[metastructure(skip_serialization = "null")]
90    pub colno: Annotated<u64>,
91
92    /// Which platform this frame is from.
93    ///
94    /// This can override the platform for a single frame. Otherwise, the platform of the event is
95    /// assumed. This can be used for multi-platform stack traces, such as in React Native.
96    #[metastructure(skip_serialization = "empty")]
97    pub platform: Annotated<String>,
98
99    /// Source code leading up to `lineno`.
100    #[metastructure(skip_serialization = "empty")]
101    pub pre_context: Annotated<Array<String>>,
102
103    /// Source code of the current line (`lineno`).
104    #[metastructure(skip_serialization = "null")]
105    pub context_line: Annotated<String>,
106
107    /// The index of the parent frame, used for flamegraphs provided by iOS.
108    #[metastructure(skip_serialization = "null")]
109    pub parent_index: Annotated<i64>,
110
111    /// The number of times this frame was sampled, used for flamegraphs provided by iOS.
112    #[metastructure(skip_serialization = "null")]
113    pub sample_count: Annotated<u64>,
114
115    /// Source code of the lines after `lineno`.
116    #[metastructure(skip_serialization = "empty")]
117    pub post_context: Annotated<Array<String>>,
118
119    /// Override whether this frame should be considered part of application code, or part of
120    /// libraries/frameworks/dependencies.
121    ///
122    /// Setting this attribute to `false` causes the frame to be hidden/collapsed by default and
123    /// mostly ignored during issue grouping.
124    #[metastructure(skip_serialization = "null")]
125    pub in_app: Annotated<bool>,
126
127    /// Mapping of local variables and expression names that were available in this frame.
128    // XXX: Probably want to trim per-var => new bag size?
129    #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
130    pub vars: Annotated<FrameVars>,
131
132    /// Auxiliary information about the frame that is platform specific.
133    #[metastructure(omit_from_schema)]
134    #[metastructure(skip_serialization = "empty")]
135    pub data: Annotated<FrameData>,
136
137    /// (C/C++/Native) Start address of the containing code module (image).
138    #[metastructure(skip_serialization = "null")]
139    pub image_addr: Annotated<Addr>,
140
141    /// (C/C++/Native) An optional instruction address for symbolication.
142    ///
143    /// This should be a string with a hexadecimal number that includes a 0x prefix.
144    /// If this is set and a known image is defined in the
145    /// [Debug Meta Interface]({%- link _documentation/development/sdk-dev/event-payloads/debugmeta.md -%}),
146    /// then symbolication can take place.
147    #[metastructure(skip_serialization = "null")]
148    pub instruction_addr: Annotated<Addr>,
149
150    /// Defines the addressing mode for addresses.
151    ///
152    /// This can be:
153    /// - `"abs"` (the default): `instruction_addr` is absolute.
154    /// - `"rel:$idx"`: `instruction_addr` is relative to the `debug_meta.image` identified by its index in the list.
155    /// - `"rel:$uuid"`: `instruction_addr` is relative to the `debug_meta.image` identified by its `debug_id`.
156    ///
157    /// If one of the `"rel:XXX"` variants is given together with `function_id`, the `instruction_addr` is relative
158    /// to the uniquely identified function in the references `debug_meta.image`.
159    #[metastructure(skip_serialization = "empty")]
160    pub addr_mode: Annotated<String>,
161
162    /// (.NET) The function id / index that uniquely identifies a function inside a module.
163    ///
164    /// This is the `MetadataToken` of a .NET `MethodBase`.
165    #[metastructure(skip_serialization = "empty")]
166    pub function_id: Annotated<Addr>,
167
168    /// (C/C++/Native) Start address of the frame's function.
169    ///
170    /// We use the instruction address for symbolication, but this can be used to calculate
171    /// an instruction offset automatically.
172    #[metastructure(skip_serialization = "null")]
173    pub symbol_addr: Annotated<Addr>,
174
175    /// (C/C++/Native) Used for native crashes to indicate how much we can "trust" the instruction_addr
176    #[metastructure(max_chars = 128)]
177    #[metastructure(omit_from_schema)]
178    pub trust: Annotated<String>,
179
180    /// The language of the frame if it overrides the stacktrace language.
181    #[metastructure(max_chars = 128)]
182    #[metastructure(omit_from_schema)]
183    pub lang: Annotated<String>,
184
185    /// Marks this frame as the bottom of a chained stack trace.
186    ///
187    /// Stack traces from asynchronous code consist of several sub traces that are chained together
188    /// into one large list. This flag indicates the root function of a chained stack trace.
189    /// Depending on the runtime and thread, this is either the `main` function or a thread base
190    /// stub.
191    ///
192    /// This field should only be specified when true.
193    #[metastructure(skip_serialization = "null")]
194    pub stack_start: Annotated<bool>,
195
196    /// A possible lock (java monitor object) held by this frame.
197    #[metastructure(skip_serialization = "null")]
198    pub lock: Annotated<LockReason>,
199
200    /// Additional arbitrary fields for forwards compatibility.
201    #[metastructure(additional_properties)]
202    pub other: Object<Value>,
203}
204
205/// Frame local variables.
206#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
207pub struct FrameVars(#[metastructure(skip_serialization = "empty")] pub Object<Value>);
208
209/// Additional frame data information.
210///
211/// This value is set by the server and should not be set by the SDK.
212#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
213pub struct FrameData {
214    /// A reference to the sourcemap used.
215    #[metastructure(max_chars = 256, max_chars_allowance = 40)]
216    sourcemap: Annotated<String>,
217    /// The original function name before it was resolved.
218    #[metastructure(max_chars = 256, max_chars_allowance = 20)]
219    orig_function: Annotated<String>,
220    /// The original minified filename.
221    #[metastructure(max_chars = 256, max_chars_allowance = 40)]
222    orig_filename: Annotated<String>,
223    /// The original line number.
224    orig_lineno: Annotated<u64>,
225    /// The original column number.
226    orig_colno: Annotated<u64>,
227    /// The original value of the in_app flag before grouping enhancers ran.
228    ///
229    /// Because we need to handle more cases the following values are used:
230    ///
231    /// - missing / `null`: information not available
232    /// - `-1`: in_app was set to `null`
233    /// - `0`: in_app was set to `false`
234    /// - `1`: in_app was set to `true`
235    orig_in_app: Annotated<i64>,
236    /// Additional keys not handled by this protocol.
237    #[metastructure(additional_properties)]
238    pub other: Object<Value>,
239}
240
241impl From<Object<Value>> for FrameVars {
242    fn from(value: Object<Value>) -> Self {
243        FrameVars(value)
244    }
245}
246
247impl FromValue for FrameVars {
248    fn from_value(mut value: Annotated<Value>) -> Annotated<FrameVars> {
249        value = value.map_value(|value| {
250            if let Value::Array(value) = value {
251                Value::Object(
252                    value
253                        .into_iter()
254                        .enumerate()
255                        .map(|(i, v)| (i.to_string(), v))
256                        .collect(),
257                )
258            } else {
259                value
260            }
261        });
262
263        FromValue::from_value(value).map_value(FrameVars)
264    }
265}
266
267/// A stack trace of a single thread.
268///
269/// A stack trace contains a list of frames, each with various bits (most optional) describing the
270/// context of that frame. Frames should be sorted from oldest to newest.
271///
272/// For the given example program written in Python:
273///
274/// ```python
275/// def foo():
276///     my_var = 'foo'
277///     raise ValueError()
278///
279/// def main():
280///     foo()
281/// ```
282///
283/// A minimalistic stack trace for the above program in the correct order:
284///
285/// ```json
286/// {
287///   "frames": [
288///     {"function": "main"},
289///     {"function": "foo"}
290///   ]
291/// }
292/// ```
293///
294/// The top frame fully symbolicated with five lines of source context:
295///
296/// ```json
297/// {
298///   "frames": [{
299///     "in_app": true,
300///     "function": "myfunction",
301///     "abs_path": "/real/file/name.py",
302///     "filename": "file/name.py",
303///     "lineno": 3,
304///     "vars": {
305///       "my_var": "'value'"
306///     },
307///     "pre_context": [
308///       "def foo():",
309///       "  my_var = 'foo'",
310///     ],
311///     "context_line": "  raise ValueError()",
312///     "post_context": [
313///       "",
314///       "def main():"
315///     ],
316///   }]
317/// }
318/// ```
319///
320/// A minimal native stack trace with register values. Note that the `package` event attribute must
321/// be "native" for these frames to be symbolicated.
322///
323/// ```json
324/// {
325///   "frames": [
326///     {"instruction_addr": "0x7fff5bf3456c"},
327///     {"instruction_addr": "0x7fff5bf346c0"},
328///   ],
329///   "registers": {
330///     "rip": "0x00007ff6eef54be2",
331///     "rsp": "0x0000003b710cd9e0"
332///   }
333/// }
334/// ```
335#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
336#[metastructure(process_func = "process_raw_stacktrace", value_type = "Stacktrace")]
337pub struct RawStacktrace {
338    /// Required. A non-empty list of stack frames. The list is ordered from caller to callee, or
339    /// oldest to youngest. The last frame is the one creating the exception.
340    #[metastructure(required = true, nonempty = true, skip_serialization = "empty")]
341    pub frames: Annotated<Array<Frame>>,
342
343    /// Register values of the thread (top frame).
344    ///
345    /// A map of register names and their values. The values should contain the actual register
346    /// values of the thread, thus mapping to the last frame in the list.
347    pub registers: Annotated<Object<RegVal>>,
348
349    /// Optional. A flag that indicates if, and how, `instruction_addr` values need to be adjusted
350    /// before they are symbolicated.
351    #[metastructure(skip_serialization = "null")]
352    pub instruction_addr_adjustment: Annotated<InstructionAddrAdjustment>,
353
354    /// The language of the stacktrace.
355    #[metastructure(max_chars = 128)]
356    pub lang: Annotated<String>,
357
358    /// Indicates that this stack trace is a snapshot triggered by an external signal.
359    ///
360    /// If this field is `false`, then the stack trace points to the code that caused this stack
361    /// trace to be created. This can be the location of a raised exception, as well as an exception
362    /// or signal handler.
363    ///
364    /// If this field is `true`, then the stack trace was captured as part of creating an unrelated
365    /// event. For example, a thread other than the crashing thread, or a stack trace computed as a
366    /// result of an external kill signal.
367    pub snapshot: Annotated<bool>,
368
369    /// Additional arbitrary fields for forwards compatibility.
370    #[metastructure(additional_properties)]
371    pub other: Object<Value>,
372}
373
374/// Controls the mechanism by which the `instruction_addr` of a [`Stacktrace`] [`Frame`] is adjusted.
375///
376/// The adjustment tries to transform *return addresses* to *call addresses* for symbolication.
377/// Typically, this adjustment needs to be done for all frames but the first, as the first frame is
378/// usually taken directly from the cpu context of a hardware exception or a suspended thread and
379/// the stack trace is created from that.
380///
381/// When the stack walking implementation truncates frames from the top, `"all"` frames should be
382/// adjusted. In case the stack walking implementation already does the adjustment when producing
383/// stack frames, `"none"` should be used here.
384#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, ProcessValue)]
385#[serde(rename_all = "snake_case")]
386#[derive(Default)]
387pub enum InstructionAddrAdjustment {
388    /// The default. Applies a heuristic based on other event / exception attributes.
389    #[default]
390    Auto,
391
392    /// All but the first frame needs to be adjusted. The first frame's address is not a *return address*,
393    /// but points directly to the faulty instruction.
394    AllButFirst,
395
396    /// All frames should be adjusted, for example because the stack walking implementation truncated
397    /// frames from the top of the stack, and all remaining frames' addresses are *return addresses*.
398    All,
399
400    /// The stack walking implementation already provides correct addresses and no adjustment should
401    /// be performed when symbolicating.
402    None,
403
404    /// Any other unknown adjustment strategy.
405    ///
406    /// This exists to ensure forward compatibility.
407    Unknown(String),
408}
409
410impl InstructionAddrAdjustment {
411    /// Returns the string representation of this adjustment.
412    pub fn as_str(&self) -> &str {
413        match self {
414            InstructionAddrAdjustment::Auto => "auto",
415            InstructionAddrAdjustment::AllButFirst => "all_but_first",
416            InstructionAddrAdjustment::All => "all",
417            InstructionAddrAdjustment::None => "none",
418            InstructionAddrAdjustment::Unknown(s) => s,
419        }
420    }
421}
422
423impl FromStr for InstructionAddrAdjustment {
424    type Err = Infallible;
425
426    fn from_str(s: &str) -> Result<Self, Self::Err> {
427        match s {
428            "auto" => Ok(Self::Auto),
429            "all_but_first" => Ok(Self::AllButFirst),
430            "all" => Ok(Self::All),
431            "none" => Ok(Self::None),
432            s => Ok(Self::Unknown(s.to_owned())),
433        }
434    }
435}
436
437impl fmt::Display for InstructionAddrAdjustment {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        f.write_str(self.as_str())
440    }
441}
442
443impl Empty for InstructionAddrAdjustment {
444    #[inline]
445    fn is_empty(&self) -> bool {
446        matches!(self, Self::Auto)
447    }
448}
449
450impl FromValue for InstructionAddrAdjustment {
451    fn from_value(value: Annotated<Value>) -> Annotated<Self> {
452        match String::from_value(value) {
453            Annotated(Some(value), mut meta) => match value.parse() {
454                Ok(adjustment) => Annotated(Some(adjustment), meta),
455                Err(_) => {
456                    meta.add_error(ErrorKind::InvalidData);
457                    meta.set_original_value(Some(value));
458                    Annotated(None, meta)
459                }
460            },
461            Annotated(None, meta) => Annotated(None, meta),
462        }
463    }
464}
465
466impl IntoValue for InstructionAddrAdjustment {
467    fn into_value(self) -> Value
468    where
469        Self: Sized,
470    {
471        Value::String(match self {
472            Self::Unknown(s) => s,
473            _ => self.as_str().to_owned(),
474        })
475    }
476
477    fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
478    where
479        Self: Sized,
480        S: serde::Serializer,
481    {
482        serde::Serialize::serialize(self.as_str(), s)
483    }
484}
485
486// NOTE: This is not a doc comment because otherwise it will show up in public docs.
487// Newtype to distinguish `raw_stacktrace` attributes from the rest.
488#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
489#[metastructure(process_func = "process_stacktrace")]
490pub struct Stacktrace(pub RawStacktrace);
491
492impl Deref for Stacktrace {
493    type Target = RawStacktrace;
494
495    fn deref(&self) -> &RawStacktrace {
496        &self.0
497    }
498}
499
500impl DerefMut for Stacktrace {
501    fn deref_mut(&mut self) -> &mut RawStacktrace {
502        &mut self.0
503    }
504}
505
506impl From<RawStacktrace> for Stacktrace {
507    fn from(stacktrace: RawStacktrace) -> Stacktrace {
508        Stacktrace(stacktrace)
509    }
510}
511
512impl From<Stacktrace> for RawStacktrace {
513    fn from(stacktrace: Stacktrace) -> RawStacktrace {
514        stacktrace.0
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use crate::protocol::{LockReasonType, ThreadId};
521    use similar_asserts::assert_eq;
522
523    use super::*;
524
525    #[test]
526    fn test_frame_roundtrip() {
527        let json = r#"{
528  "function": "main@8",
529  "raw_function": "main",
530  "symbol": "_main@8",
531  "module": "app",
532  "package": "/my/app",
533  "filename": "myfile.rs",
534  "abs_path": "/path/to",
535  "lineno": 2,
536  "colno": 42,
537  "platform": "rust",
538  "pre_context": [
539    "fn main() {"
540  ],
541  "context_line": "unimplemented!()",
542  "post_context": [
543    "}"
544  ],
545  "in_app": true,
546  "vars": {
547    "variable": "value"
548  },
549  "data": {
550    "sourcemap": "http://example.com/invalid.map"
551  },
552  "image_addr": "0x400",
553  "instruction_addr": "0x404",
554  "addr_mode": "abs",
555  "symbol_addr": "0x404",
556  "trust": "69",
557  "lang": "rust",
558  "stack_start": true,
559  "lock": {
560    "type": 2,
561    "address": "0x07d7437b",
562    "package_name": "io.sentry.samples",
563    "class_name": "MainActivity",
564    "thread_id": 7
565  },
566  "other": "value"
567}"#;
568        let frame = Annotated::new(Frame {
569            function: Annotated::new("main@8".to_owned()),
570            raw_function: Annotated::new("main".to_owned()),
571            symbol: Annotated::new("_main@8".to_owned()),
572            module: Annotated::new("app".to_owned()),
573            package: Annotated::new("/my/app".to_owned()),
574            filename: Annotated::new("myfile.rs".into()),
575            abs_path: Annotated::new("/path/to".into()),
576            lineno: Annotated::new(2),
577            colno: Annotated::new(42),
578            platform: Annotated::new("rust".to_owned()),
579            pre_context: Annotated::new(vec![Annotated::new("fn main() {".to_owned())]),
580            context_line: Annotated::new("unimplemented!()".to_owned()),
581            post_context: Annotated::new(vec![Annotated::new("}".to_owned())]),
582            in_app: Annotated::new(true),
583            vars: {
584                let mut vars = Object::new();
585                vars.insert(
586                    "variable".to_owned(),
587                    Annotated::new(Value::String("value".to_owned())),
588                );
589                Annotated::new(vars.into())
590            },
591            data: Annotated::new(FrameData {
592                sourcemap: Annotated::new("http://example.com/invalid.map".to_owned()),
593                ..Default::default()
594            }),
595            image_addr: Annotated::new(Addr(0x400)),
596            instruction_addr: Annotated::new(Addr(0x404)),
597            addr_mode: Annotated::new("abs".into()),
598            function_id: Annotated::empty(),
599            symbol_addr: Annotated::new(Addr(0x404)),
600            trust: Annotated::new("69".into()),
601            lang: Annotated::new("rust".into()),
602            stack_start: Annotated::new(true),
603            lock: Annotated::new(LockReason {
604                ty: Annotated::new(LockReasonType::Waiting),
605                address: Annotated::new("0x07d7437b".to_owned()),
606                package_name: Annotated::new("io.sentry.samples".to_owned()),
607                class_name: Annotated::new("MainActivity".to_owned()),
608                thread_id: Annotated::new(ThreadId::Int(7)),
609                other: Default::default(),
610            }),
611            parent_index: Annotated::empty(),
612            sample_count: Annotated::empty(),
613            other: {
614                let mut vars = Object::new();
615                vars.insert(
616                    "other".to_owned(),
617                    Annotated::new(Value::String("value".to_owned())),
618                );
619                vars
620            },
621        });
622
623        assert_eq!(frame, Annotated::from_json(json).unwrap());
624        assert_eq!(json, frame.to_json_pretty().unwrap());
625    }
626
627    #[test]
628    fn test_frame_default_values() {
629        let json = "{}";
630        let frame = Annotated::new(Frame::default());
631
632        assert_eq!(frame, Annotated::from_json(json).unwrap());
633        assert_eq!(json, frame.to_json_pretty().unwrap());
634    }
635
636    #[test]
637    fn test_stacktrace_roundtrip() {
638        let json = r#"{
639  "frames": [
640    {
641      "function": "foobar"
642    }
643  ],
644  "registers": {
645    "cspr": "0x20000000",
646    "lr": "0x18a31aadc",
647    "pc": "0x18a310ea4",
648    "sp": "0x16fd75060"
649  },
650  "instruction_addr_adjustment": "all_but_first",
651  "lang": "rust",
652  "snapshot": false,
653  "other": "value"
654}"#;
655        let stack = Annotated::new(RawStacktrace {
656            frames: Annotated::new(vec![Annotated::new(Frame {
657                function: Annotated::new("foobar".to_owned()),
658                ..Default::default()
659            })]),
660            registers: {
661                let mut registers = Object::new();
662                registers.insert("cspr".to_owned(), Annotated::new(RegVal(0x2000_0000)));
663                registers.insert("lr".to_owned(), Annotated::new(RegVal(0x1_8a31_aadc)));
664                registers.insert("pc".to_owned(), Annotated::new(RegVal(0x1_8a31_0ea4)));
665                registers.insert("sp".to_owned(), Annotated::new(RegVal(0x1_6fd7_5060)));
666                Annotated::new(registers)
667            },
668            instruction_addr_adjustment: Annotated::new(InstructionAddrAdjustment::AllButFirst),
669            lang: Annotated::new("rust".into()),
670            snapshot: Annotated::new(false),
671            other: {
672                let mut other = Object::new();
673                other.insert(
674                    "other".to_owned(),
675                    Annotated::new(Value::String("value".to_owned())),
676                );
677                other
678            },
679        });
680
681        assert_eq!(stack, Annotated::from_json(json).unwrap());
682        assert_eq!(json, stack.to_json_pretty().unwrap());
683    }
684
685    #[test]
686    fn test_stacktrace_default_values() {
687        // This needs an empty frame because "frames" is required
688        let json = r#"{
689  "frames": [
690    {}
691  ]
692}"#;
693
694        let stack = Annotated::new(RawStacktrace {
695            frames: Annotated::new(vec![Annotated::new(Frame::default())]),
696            ..Default::default()
697        });
698
699        assert_eq!(stack, Annotated::from_json(json).unwrap());
700        assert_eq!(json, stack.to_json_pretty().unwrap());
701    }
702
703    #[test]
704    fn test_frame_vars_null_preserved() {
705        let json = r#"{
706  "vars": {
707    "despacito": null
708  }
709}"#;
710        let frame = Annotated::new(Frame {
711            vars: Annotated::new({
712                let mut vars = Object::new();
713                vars.insert("despacito".to_owned(), Annotated::empty());
714                vars.into()
715            }),
716            ..Default::default()
717        });
718
719        assert_eq!(Annotated::from_json(json).unwrap(), frame);
720        assert_eq!(json, frame.to_json_pretty().unwrap());
721    }
722
723    #[test]
724    fn test_frame_vars_empty_annotated_is_serialized() {
725        let output = r#"{
726  "vars": {
727    "despacito": null,
728    "despacito2": 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.insert("despacito2".to_owned(), Annotated::empty());
736                vars.into()
737            }),
738            ..Default::default()
739        });
740
741        assert_eq!(output, frame.to_json_pretty().unwrap());
742    }
743
744    #[test]
745    fn test_frame_empty_context_lines() {
746        let json = r#"{
747  "pre_context": [
748    ""
749  ],
750  "context_line": "",
751  "post_context": [
752    ""
753  ]
754}"#;
755
756        let frame = Annotated::new(Frame {
757            pre_context: Annotated::new(vec![Annotated::new("".to_owned())]),
758            context_line: Annotated::new("".to_owned()),
759            post_context: Annotated::new(vec![Annotated::new("".to_owned())]),
760            ..Frame::default()
761        });
762
763        assert_eq!(frame, Annotated::from_json(json).unwrap());
764        assert_eq!(json, frame.to_json_pretty().unwrap());
765    }
766
767    #[test]
768    fn test_php_frame_vars() {
769        // Buggy PHP SDKs send us this stuff
770        //
771        // Port of https://github.com/getsentry/sentry/commit/73d9a061dcac3ab8c318a09735601a12e81085dd
772
773        let input = r#"{
774  "vars": ["foo", "bar", "baz", null]
775}"#;
776
777        let output = r#"{
778  "vars": {
779    "0": "foo",
780    "1": "bar",
781    "2": "baz",
782    "3": null
783  }
784}"#;
785
786        let frame = Annotated::new(Frame {
787            vars: Annotated::new({
788                let mut vars = Object::new();
789                vars.insert("0".to_owned(), Annotated::new("foo".to_owned().into()));
790                vars.insert("1".to_owned(), Annotated::new("bar".to_owned().into()));
791                vars.insert("2".to_owned(), Annotated::new("baz".to_owned().into()));
792                vars.insert("3".to_owned(), Annotated::empty());
793                vars.into()
794            }),
795            ..Default::default()
796        });
797
798        assert_eq!(frame, Annotated::from_json(input).unwrap());
799        assert_eq!(output, frame.to_json_pretty().unwrap());
800    }
801}