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