relay_event_schema/protocol/
debugmeta.rs

1use std::fmt;
2use std::ops::{Deref, DerefMut};
3use std::str::FromStr;
4
5use enumset::EnumSet;
6use relay_protocol::{
7    Annotated, Array, Empty, Error, FromValue, IntoValue, Meta, Object, SkipSerialization, Value,
8};
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12use crate::processor::{ProcessValue, ProcessingResult, ProcessingState, Processor, ValueType};
13use crate::protocol::Addr;
14
15/// A type for strings that are generally paths, might contain system user names, but still cannot
16/// be stripped liberally because it would break processing for certain platforms.
17///
18/// Those strings get special treatment in our PII processor to avoid stripping the basename.
19#[derive(Debug, FromValue, IntoValue, Empty, Clone, PartialEq, Deserialize, Serialize)]
20pub struct NativeImagePath(pub String);
21
22impl NativeImagePath {
23    pub fn as_str(&self) -> &str {
24        self.0.as_str()
25    }
26}
27
28impl<T: Into<String>> From<T> for NativeImagePath {
29    fn from(value: T) -> NativeImagePath {
30        NativeImagePath(value.into())
31    }
32}
33
34impl ProcessValue for NativeImagePath {
35    #[inline]
36    fn value_type(&self) -> EnumSet<ValueType> {
37        // Explicit decision not to expose NativeImagePath as valuetype, as people should not be
38        // able to address processing internals.
39        //
40        // Also decided against exposing a $filepath ("things that may contain filenames") because
41        // ruletypes/regexes are better suited for this, and in the case of $frame.package (where
42        // it depends on platform) it's really not that useful.
43        EnumSet::only(ValueType::String)
44    }
45
46    #[inline]
47    fn process_value<P>(
48        &mut self,
49        meta: &mut Meta,
50        processor: &mut P,
51        state: &ProcessingState<'_>,
52    ) -> ProcessingResult
53    where
54        P: Processor,
55    {
56        processor.process_native_image_path(self, meta, state)
57    }
58
59    fn process_child_values<P>(
60        &mut self,
61        _processor: &mut P,
62        _state: &ProcessingState<'_>,
63    ) -> ProcessingResult
64    where
65        P: Processor,
66    {
67        Ok(())
68    }
69}
70
71/// Holds information about the system SDK.
72///
73/// This is relevant for iOS and other platforms that have a system
74/// SDK.  Not to be confused with the client SDK.
75#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
76pub struct SystemSdkInfo {
77    /// The internal name of the SDK.
78    pub sdk_name: Annotated<String>,
79
80    /// The major version of the SDK as integer or 0.
81    pub version_major: Annotated<u64>,
82
83    /// The minor version of the SDK as integer or 0.
84    pub version_minor: Annotated<u64>,
85
86    /// The patch version of the SDK as integer or 0.
87    pub version_patchlevel: Annotated<u64>,
88
89    /// Additional arbitrary fields for forwards compatibility.
90    #[metastructure(additional_properties)]
91    pub other: Object<Value>,
92}
93
94/// Legacy apple debug images (MachO).
95///
96/// This was also used for non-apple platforms with similar debug setups.
97#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
98pub struct AppleDebugImage {
99    /// Path and name of the debug image (required).
100    #[metastructure(required = true)]
101    pub name: Annotated<String>,
102
103    /// CPU architecture target.
104    pub arch: Annotated<String>,
105
106    /// MachO CPU type identifier.
107    pub cpu_type: Annotated<u64>,
108
109    /// MachO CPU subtype identifier.
110    pub cpu_subtype: Annotated<u64>,
111
112    /// Starting memory address of the image (required).
113    #[metastructure(required = true)]
114    pub image_addr: Annotated<Addr>,
115
116    /// Size of the image in bytes (required).
117    #[metastructure(required = true)]
118    pub image_size: Annotated<u64>,
119
120    /// Loading address in virtual memory.
121    pub image_vmaddr: Annotated<Addr>,
122
123    /// The unique UUID of the image.
124    #[metastructure(required = true)]
125    pub uuid: Annotated<Uuid>,
126
127    /// Additional arbitrary fields for forwards compatibility.
128    #[metastructure(additional_properties)]
129    pub other: Object<Value>,
130}
131
132macro_rules! impl_traits {
133    ($type:ident, $inner:path, $expectation:literal) => {
134        impl Empty for $type {
135            #[inline]
136            fn is_empty(&self) -> bool {
137                self.is_nil()
138            }
139        }
140
141        impl FromValue for $type {
142            fn from_value(value: Annotated<Value>) -> Annotated<Self> {
143                match value {
144                    Annotated(Some(Value::String(value)), mut meta) => match value.parse() {
145                        Ok(value) => Annotated(Some(value), meta),
146                        Err(err) => {
147                            meta.add_error(Error::invalid(err));
148                            meta.set_original_value(Some(value));
149                            Annotated(None, meta)
150                        }
151                    },
152                    Annotated(Some(value), mut meta) => {
153                        meta.add_error(Error::expected($expectation));
154                        meta.set_original_value(Some(value));
155                        Annotated(None, meta)
156                    }
157                    Annotated(None, meta) => Annotated(None, meta),
158                }
159            }
160        }
161
162        impl IntoValue for $type {
163            fn into_value(self) -> Value {
164                Value::String(self.to_string())
165            }
166
167            fn serialize_payload<S>(
168                &self,
169                s: S,
170                _behavior: SkipSerialization,
171            ) -> Result<S::Ok, S::Error>
172            where
173                S: serde::Serializer,
174            {
175                serde::Serialize::serialize(self, s)
176            }
177        }
178
179        impl ProcessValue for $type {}
180
181        impl fmt::Display for $type {
182            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183                self.0.fmt(f)
184            }
185        }
186
187        impl FromStr for $type {
188            type Err = <$inner as FromStr>::Err;
189
190            fn from_str(s: &str) -> Result<Self, Self::Err> {
191                FromStr::from_str(s).map($type)
192            }
193        }
194
195        impl Deref for $type {
196            type Target = $inner;
197
198            fn deref(&self) -> &Self::Target {
199                &self.0
200            }
201        }
202
203        impl DerefMut for $type {
204            fn deref_mut(&mut self) -> &mut Self::Target {
205                &mut self.0
206            }
207        }
208    };
209}
210
211#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
212pub struct DebugId(pub debugid::DebugId);
213
214#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
215pub struct CodeId(pub debugid::CodeId);
216
217impl_traits!(CodeId, debugid::CodeId, "a code identifier");
218impl_traits!(DebugId, debugid::DebugId, "a debug identifier");
219
220impl<T> From<T> for DebugId
221where
222    debugid::DebugId: From<T>,
223{
224    fn from(t: T) -> Self {
225        DebugId(t.into())
226    }
227}
228
229impl<T> From<T> for CodeId
230where
231    debugid::CodeId: From<T>,
232{
233    fn from(t: T) -> Self {
234        CodeId(t.into())
235    }
236}
237
238/// A generic (new-style) native platform debug information file.
239///
240/// The `type` key must be one of:
241///
242/// - `macho`
243/// - `elf`: ELF images are used on Linux platforms. Their structure is identical to other native images.
244/// - `pe`
245///
246/// Examples:
247///
248/// ```json
249/// {
250///   "type": "elf",
251///   "code_id": "68220ae2c65d65c1b6aaa12fa6765a6ec2f5f434",
252///   "code_file": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
253///   "debug_id": "e20a2268-5dc6-c165-b6aa-a12fa6765a6e",
254///   "image_addr": "0x7f5140527000",
255///   "image_size": 90112,
256///   "image_vmaddr": "0x40000",
257///   "arch": "x86_64"
258/// }
259/// ```
260///
261/// ```json
262/// {
263///   "type": "pe",
264///   "code_id": "57898e12145000",
265///   "code_file": "C:\\Windows\\System32\\dbghelp.dll",
266///   "debug_id": "9c2a902b-6fdf-40ad-8308-588a41d572a0-1",
267///   "debug_file": "dbghelp.pdb",
268///   "image_addr": "0x70850000",
269///   "image_size": "1331200",
270///   "image_vmaddr": "0x40000",
271///   "arch": "x86"
272/// }
273/// ```
274///
275/// ```json
276/// {
277///   "type": "macho",
278///   "debug_id": "84a04d24-0e60-3810-a8c0-90a65e2df61a",
279///   "debug_file": "libDiagnosticMessagesClient.dylib",
280///   "code_file": "/usr/lib/libDiagnosticMessagesClient.dylib",
281///   "image_addr": "0x7fffe668e000",
282///   "image_size": 8192,
283///   "image_vmaddr": "0x40000",
284///   "arch": "x86_64",
285/// }
286/// ```
287#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
288pub struct NativeDebugImage {
289    /// Optional identifier of the code file.
290    ///
291    /// - `elf`: If the program was compiled with a relatively recent compiler, this should be the hex representation of the `NT_GNU_BUILD_ID` program header (type `PT_NOTE`), or the value of the `.note.gnu.build-id` note section (type `SHT_NOTE`). Otherwise, leave this value empty.
292    ///
293    ///   Certain symbol servers use the code identifier to locate debug information for ELF images, in which case this field should be included if possible.
294    ///
295    /// - `pe`: Identifier of the executable or DLL. It contains the values of the `time_date_stamp` from the COFF header and `size_of_image` from the optional header formatted together into a hex string using `%08x%X` (note that the second value is not padded):
296    ///
297    ///   ```text
298    ///   time_date_stamp: 0x5ab38077
299    ///   size_of_image:           0x9000
300    ///   code_id:           5ab380779000
301    ///   ```
302    ///
303    ///   The code identifier should be provided to allow server-side stack walking of binary crash reports, such as Minidumps.
304    ///
305    ///
306    /// - `macho`: Identifier of the dynamic library or executable. It is the value of the `LC_UUID` load command in the Mach header, formatted as UUID. Can be empty for Mach images, as it is equivalent to the debug identifier.
307    pub code_id: Annotated<CodeId>,
308
309    /// Path and name of the image file (required).
310    ///
311    /// The absolute path to the dynamic library or executable. This helps to locate the file if it is missing on Sentry.
312    ///
313    /// - `pe`: The code file should be provided to allow server-side stack walking of binary crash reports, such as Minidumps.
314    #[metastructure(required = true, legacy_alias = "name")]
315    #[metastructure(pii = "maybe")]
316    pub code_file: Annotated<NativeImagePath>,
317
318    /// Unique debug identifier of the image.
319    ///
320    /// - `elf`: Debug identifier of the dynamic library or executable. If a code identifier is available, the debug identifier is the little-endian UUID representation of the first 16-bytes of that
321    ///   identifier. Spaces are inserted for readability, note the byte order of the first fields:
322    ///
323    ///   ```text
324    ///   code id:  f1c3bcc0 2798 65fe 3058 404b2831d9e6 4135386c
325    ///   debug id: c0bcc3f1-9827-fe65-3058-404b2831d9e6
326    ///   ```
327    ///
328    ///   If no code id is available, the debug id should be computed by XORing the first 4096 bytes of the `.text` section in 16-byte chunks, and representing it as a little-endian UUID (again swapping the byte order).
329    ///
330    /// - `pe`: `signature` and `age` of the PDB file. Both values can be read from the CodeView PDB70 debug information header in the PE. The value should be represented as little-endian UUID, with the age appended at the end. Note that the byte order of the UUID fields must be swapped (spaces inserted for readability):
331    ///
332    ///   ```text
333    ///   signature: f1c3bcc0 2798 65fe 3058 404b2831d9e6
334    ///   age:                                            1
335    ///   debug_id:  c0bcc3f1-9827-fe65-3058-404b2831d9e6-1
336    ///   ```
337    ///
338    /// - `macho`: Identifier of the dynamic library or executable. It is the value of the `LC_UUID` load command in the Mach header, formatted as UUID.
339    #[metastructure(required = true, legacy_alias = "id")]
340    pub debug_id: Annotated<DebugId>,
341
342    /// Path and name of the debug companion file.
343    ///
344    /// - `elf`: Name or absolute path to the file containing stripped debug information for this image. This value might be _required_ to retrieve debug files from certain symbol servers.
345    ///
346    /// - `pe`: Name of the PDB file containing debug information for this image. This value is often required to retrieve debug files from specific symbol servers.
347    ///
348    /// - `macho`: Name or absolute path to the dSYM file containing debug information for this image. This value might be required to retrieve debug files from certain symbol servers.
349    #[metastructure(pii = "maybe")]
350    pub debug_file: Annotated<NativeImagePath>,
351
352    /// The optional checksum of the debug companion file.
353    ///
354    /// - `pe_dotnet`: This is the hash algorithm and hex-formatted checksum of the associated PDB file.
355    ///   This should have the format `$algorithm:$hash`, for example `SHA256:aabbccddeeff...`.
356    ///
357    ///   See: <https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md#pdb-checksum-debug-directory-entry-type-19>
358    pub debug_checksum: Annotated<String>,
359
360    /// CPU architecture target.
361    ///
362    /// Architecture of the module. If missing, this will be backfilled by Sentry.
363    pub arch: Annotated<String>,
364
365    /// Starting memory address of the image (required).
366    ///
367    /// Memory address, at which the image is mounted in the virtual address space of the process. Should be a string in hex representation prefixed with `"0x"`.
368    pub image_addr: Annotated<Addr>,
369
370    /// Size of the image in bytes (required).
371    ///
372    /// The size of the image in virtual memory. If missing, Sentry will assume that the image spans up to the next image, which might lead to invalid stack traces.
373    pub image_size: Annotated<u64>,
374
375    /// Loading address in virtual memory.
376    ///
377    /// Preferred load address of the image in virtual memory, as declared in the headers of the
378    /// image. When loading an image, the operating system may still choose to place it at a
379    /// different address.
380    ///
381    /// Symbols and addresses in the native image are always relative to the start of the image and do not consider the preferred load address. It is merely a hint to the loader.
382    ///
383    /// - `elf`/`macho`: If this value is non-zero, all symbols and addresses declared in the native image start at this address, rather than 0. By contrast, Sentry deals with addresses relative to the start of the image. For example, with `image_vmaddr: 0x40000`, a symbol located at `0x401000` has a relative address of `0x1000`.
384    ///
385    ///   Relative addresses used in Apple Crash Reports and `addr2line` are usually in the preferred address space, and not relative address space.
386    pub image_vmaddr: Annotated<Addr>,
387
388    /// Additional arbitrary fields for forwards compatibility.
389    #[metastructure(additional_properties)]
390    pub other: Object<Value>,
391}
392
393/// A debug image pointing to a source map.
394///
395/// Examples:
396///
397/// ```json
398/// {
399///   "type": "sourcemap",
400///   "code_file": "https://example.com/static/js/main.min.js",
401///   "debug_id": "395835f4-03e0-4436-80d3-136f0749a893"
402/// }
403/// ```
404///
405/// **Note:** Stack frames and the correlating entries in the debug image here
406/// for `code_file`/`abs_path` are not PII stripped as they need to line up
407/// perfectly for source map processing.
408#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
409pub struct SourceMapDebugImage {
410    /// Path and name of the image file as URL. (required).
411    ///
412    /// The absolute path to the minified JavaScript file.  This helps to correlate the file to the stack trace.
413    #[metastructure(required = true)]
414    pub code_file: Annotated<String>,
415
416    /// Unique debug identifier of the source map.
417    #[metastructure(required = true)]
418    pub debug_id: Annotated<DebugId>,
419
420    /// Path and name of the associated source map.
421    #[metastructure(pii = "maybe")]
422    pub debug_file: Annotated<String>,
423
424    /// Additional arbitrary fields for forwards compatibility.
425    #[metastructure(additional_properties)]
426    pub other: Object<Value>,
427}
428
429/// A debug image consisting of source files for a JVM based language.
430///
431/// Examples:
432///
433/// ```json
434/// {
435///   "type": "jvm",
436///   "debug_id": "395835f4-03e0-4436-80d3-136f0749a893"
437/// }
438/// ```
439#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
440pub struct JvmDebugImage {
441    /// Unique debug identifier of the bundle.
442    #[metastructure(required = true)]
443    pub debug_id: Annotated<DebugId>,
444
445    /// Additional arbitrary fields for forwards compatibility.
446    #[metastructure(additional_properties)]
447    pub other: Object<Value>,
448}
449
450/// Proguard mapping file.
451///
452/// Proguard images refer to `mapping.txt` files generated when Proguard obfuscates function names. The Java SDK integrations assign this file a unique identifier, which has to be included in the list of images.
453#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
454pub struct ProguardDebugImage {
455    /// UUID computed from the file contents, assigned by the Java SDK.
456    #[metastructure(required = true)]
457    pub uuid: Annotated<Uuid>,
458
459    /// Additional arbitrary fields for forwards compatibility.
460    #[metastructure(additional_properties)]
461    pub other: Object<Value>,
462}
463
464/// A debug information file (debug image).
465#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
466#[metastructure(process_func = "process_debug_image")]
467pub enum DebugImage {
468    /// Legacy apple debug images (MachO).
469    Apple(Box<AppleDebugImage>),
470    /// A generic (new-style) native platform debug information file.
471    Symbolic(Box<NativeDebugImage>),
472    /// MachO (macOS and iOS) debug image.
473    MachO(Box<NativeDebugImage>),
474    /// ELF (Linux) debug image.
475    Elf(Box<NativeDebugImage>),
476    /// PE (Windows) debug image.
477    Pe(Box<NativeDebugImage>),
478    /// .NET PE debug image with associated Portable PDB debug companion.
479    #[metastructure(tag = "pe_dotnet")]
480    PeDotnet(Box<NativeDebugImage>),
481    /// A reference to a proguard debug file.
482    Proguard(Box<ProguardDebugImage>),
483    /// WASM debug image.
484    Wasm(Box<NativeDebugImage>),
485    /// Source map debug image.
486    SourceMap(Box<SourceMapDebugImage>),
487    /// JVM based debug image.
488    Jvm(Box<JvmDebugImage>),
489    /// A debug image that is unknown to this protocol specification.
490    #[metastructure(fallback_variant)]
491    Other(Object<Value>),
492}
493
494/// Debugging and processing meta information.
495///
496/// The debug meta interface carries debug information for processing errors and crash reports.
497/// Sentry amends the information in this interface.
498///
499/// Example (look at field types to see more detail):
500///
501/// ```json
502/// {
503///   "debug_meta": {
504///     "images": [],
505///     "sdk_info": {
506///       "sdk_name": "iOS",
507///       "version_major": 10,
508///       "version_minor": 3,
509///       "version_patchlevel": 0
510///     }
511///   }
512/// }
513/// ```
514#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
515#[metastructure(process_func = "process_debug_meta")]
516pub struct DebugMeta {
517    /// Information about the system SDK (e.g. iOS SDK).
518    #[metastructure(field = "sdk_info")]
519    #[metastructure(skip_serialization = "empty")]
520    pub system_sdk: Annotated<SystemSdkInfo>,
521
522    /// List of debug information files (debug images).
523    #[metastructure(skip_serialization = "empty")]
524    pub images: Annotated<Array<DebugImage>>,
525
526    /// Additional arbitrary fields for forwards compatibility.
527    #[metastructure(additional_properties)]
528    pub other: Object<Value>,
529}
530
531#[cfg(test)]
532mod tests {
533    use relay_protocol::Map;
534    use similar_asserts::assert_eq;
535
536    use super::*;
537
538    #[test]
539    fn test_debug_image_proguard_roundtrip() {
540        let json = r#"{
541  "uuid": "395835f4-03e0-4436-80d3-136f0749a893",
542  "other": "value",
543  "type": "proguard"
544}"#;
545        let image = Annotated::new(DebugImage::Proguard(Box::new(ProguardDebugImage {
546            uuid: Annotated::new("395835f4-03e0-4436-80d3-136f0749a893".parse().unwrap()),
547            other: {
548                let mut map = Object::new();
549                map.insert(
550                    "other".to_string(),
551                    Annotated::new(Value::String("value".to_string())),
552                );
553                map
554            },
555        })));
556
557        assert_eq!(image, Annotated::from_json(json).unwrap());
558        assert_eq!(json, image.to_json_pretty().unwrap());
559    }
560
561    #[test]
562    fn test_debug_image_jvm_based_roundtrip() {
563        let json = r#"{
564  "debug_id": "395835f4-03e0-4436-80d3-136f0749a893",
565  "other": "value",
566  "type": "jvm"
567}"#;
568        let image = Annotated::new(DebugImage::Jvm(Box::new(JvmDebugImage {
569            debug_id: Annotated::new("395835f4-03e0-4436-80d3-136f0749a893".parse().unwrap()),
570            other: {
571                let mut map = Map::new();
572                map.insert(
573                    "other".to_string(),
574                    Annotated::new(Value::String("value".to_string())),
575                );
576                map
577            },
578        })));
579
580        assert_eq!(image, Annotated::from_json(json).unwrap());
581        assert_eq!(json, image.to_json_pretty().unwrap());
582    }
583
584    #[test]
585    fn test_debug_image_apple_roundtrip() {
586        let json = r#"{
587  "name": "CoreFoundation",
588  "arch": "arm64",
589  "cpu_type": 1233,
590  "cpu_subtype": 3,
591  "image_addr": "0x0",
592  "image_size": 4096,
593  "image_vmaddr": "0x8000",
594  "uuid": "494f3aea-88fa-4296-9644-fa8ef5d139b6",
595  "other": "value",
596  "type": "apple"
597}"#;
598
599        let image = Annotated::new(DebugImage::Apple(Box::new(AppleDebugImage {
600            name: Annotated::new("CoreFoundation".to_string()),
601            arch: Annotated::new("arm64".to_string()),
602            cpu_type: Annotated::new(1233),
603            cpu_subtype: Annotated::new(3),
604            image_addr: Annotated::new(Addr(0)),
605            image_size: Annotated::new(4096),
606            image_vmaddr: Annotated::new(Addr(32768)),
607            uuid: Annotated::new("494f3aea-88fa-4296-9644-fa8ef5d139b6".parse().unwrap()),
608            other: {
609                let mut map = Object::new();
610                map.insert(
611                    "other".to_string(),
612                    Annotated::new(Value::String("value".to_string())),
613                );
614                map
615            },
616        })));
617
618        assert_eq!(image, Annotated::from_json(json).unwrap());
619        assert_eq!(json, image.to_json_pretty().unwrap());
620    }
621
622    #[test]
623    fn test_debug_image_apple_default_values() {
624        let json = r#"{
625  "name": "CoreFoundation",
626  "image_addr": "0x0",
627  "image_size": 4096,
628  "uuid": "494f3aea-88fa-4296-9644-fa8ef5d139b6",
629  "type": "apple"
630}"#;
631
632        let image = Annotated::new(DebugImage::Apple(Box::new(AppleDebugImage {
633            name: Annotated::new("CoreFoundation".to_string()),
634            image_addr: Annotated::new(Addr(0)),
635            image_size: Annotated::new(4096),
636            uuid: Annotated::new("494f3aea-88fa-4296-9644-fa8ef5d139b6".parse().unwrap()),
637            ..Default::default()
638        })));
639
640        assert_eq!(image, Annotated::from_json(json).unwrap());
641        assert_eq!(json, image.to_json_pretty().unwrap());
642    }
643
644    #[test]
645    fn test_debug_image_symbolic_roundtrip() {
646        let json = r#"{
647  "code_id": "59b0d8f3183000",
648  "code_file": "C:\\Windows\\System32\\ntdll.dll",
649  "debug_id": "971f98e5-ce60-41ff-b2d7-235bbeb34578-1",
650  "debug_file": "wntdll.pdb",
651  "arch": "arm64",
652  "image_addr": "0x0",
653  "image_size": 4096,
654  "image_vmaddr": "0x8000",
655  "other": "value",
656  "type": "symbolic"
657}"#;
658
659        let image = Annotated::new(DebugImage::Symbolic(Box::new(NativeDebugImage {
660            code_id: Annotated::new("59b0d8f3183000".parse().unwrap()),
661            code_file: Annotated::new("C:\\Windows\\System32\\ntdll.dll".into()),
662            debug_id: Annotated::new("971f98e5-ce60-41ff-b2d7-235bbeb34578-1".parse().unwrap()),
663            debug_file: Annotated::new("wntdll.pdb".into()),
664            debug_checksum: Annotated::empty(),
665            arch: Annotated::new("arm64".to_string()),
666            image_addr: Annotated::new(Addr(0)),
667            image_size: Annotated::new(4096),
668            image_vmaddr: Annotated::new(Addr(32768)),
669            other: {
670                let mut map = Object::new();
671                map.insert(
672                    "other".to_string(),
673                    Annotated::new(Value::String("value".to_string())),
674                );
675                map
676            },
677        })));
678
679        assert_eq!(image, Annotated::from_json(json).unwrap());
680        assert_eq!(json, image.to_json_pretty().unwrap());
681    }
682
683    #[test]
684    fn test_debug_image_symbolic_legacy() {
685        let json = r#"{
686  "name": "CoreFoundation",
687  "arch": "arm64",
688  "image_addr": "0x0",
689  "image_size": 4096,
690  "image_vmaddr": "0x8000",
691  "id": "494f3aea-88fa-4296-9644-fa8ef5d139b6-1234",
692  "other": "value",
693  "type": "symbolic"
694}"#;
695
696        let image = Annotated::new(DebugImage::Symbolic(Box::new(NativeDebugImage {
697            code_id: Annotated::empty(),
698            code_file: Annotated::new("CoreFoundation".into()),
699            debug_id: Annotated::new("494f3aea-88fa-4296-9644-fa8ef5d139b6-1234".parse().unwrap()),
700            debug_file: Annotated::empty(),
701            debug_checksum: Annotated::empty(),
702            arch: Annotated::new("arm64".to_string()),
703            image_addr: Annotated::new(Addr(0)),
704            image_size: Annotated::new(4096),
705            image_vmaddr: Annotated::new(Addr(32768)),
706            other: {
707                let mut map = Object::new();
708                map.insert(
709                    "other".to_string(),
710                    Annotated::new(Value::String("value".to_string())),
711                );
712                map
713            },
714        })));
715
716        assert_eq!(image, Annotated::from_json(json).unwrap());
717    }
718
719    #[test]
720    fn test_debug_image_symbolic_default_values() {
721        let json = r#"{
722  "code_file": "CoreFoundation",
723  "debug_id": "494f3aea-88fa-4296-9644-fa8ef5d139b6-1234",
724  "image_addr": "0x0",
725  "image_size": 4096,
726  "type": "symbolic"
727}"#;
728
729        let image = Annotated::new(DebugImage::Symbolic(Box::new(NativeDebugImage {
730            code_file: Annotated::new("CoreFoundation".into()),
731            debug_id: Annotated::new(
732                "494f3aea-88fa-4296-9644-fa8ef5d139b6-1234"
733                    .parse::<DebugId>()
734                    .unwrap(),
735            ),
736            image_addr: Annotated::new(Addr(0)),
737            image_size: Annotated::new(4096),
738            ..Default::default()
739        })));
740
741        assert_eq!(image, Annotated::from_json(json).unwrap());
742        assert_eq!(json, image.to_json_pretty().unwrap());
743    }
744
745    #[test]
746    fn test_debug_image_elf_roundtrip() {
747        let json = r#"{
748  "code_id": "f1c3bcc0279865fe3058404b2831d9e64135386c",
749  "code_file": "crash",
750  "debug_id": "c0bcc3f1-9827-fe65-3058-404b2831d9e6",
751  "arch": "arm64",
752  "image_addr": "0x0",
753  "image_size": 4096,
754  "image_vmaddr": "0x8000",
755  "other": "value",
756  "type": "elf"
757}"#;
758
759        let image = Annotated::new(DebugImage::Elf(Box::new(NativeDebugImage {
760            code_id: Annotated::new("f1c3bcc0279865fe3058404b2831d9e64135386c".parse().unwrap()),
761            code_file: Annotated::new("crash".into()),
762            debug_id: Annotated::new("c0bcc3f1-9827-fe65-3058-404b2831d9e6".parse().unwrap()),
763            debug_file: Annotated::empty(),
764            debug_checksum: Annotated::empty(),
765            arch: Annotated::new("arm64".to_string()),
766            image_addr: Annotated::new(Addr(0)),
767            image_size: Annotated::new(4096),
768            image_vmaddr: Annotated::new(Addr(32768)),
769            other: {
770                let mut map = Object::new();
771                map.insert(
772                    "other".to_string(),
773                    Annotated::new(Value::String("value".to_string())),
774                );
775                map
776            },
777        })));
778
779        assert_eq!(image, Annotated::from_json(json).unwrap());
780        assert_eq!(json, image.to_json_pretty().unwrap());
781    }
782
783    #[test]
784    fn test_debug_image_pe_dotnet_roundtrip() {
785        let json = r#"{
786  "debug_id": "4e2ca887-825e-46f3-968f-25b41ae1b5f3-cc3f6d9e",
787  "debug_file": "TimeZoneConverter.pdb",
788  "debug_checksum": "SHA256:87a82c4e5e82f386968f25b41ae1b5f3cc3f6d9e79cfb4464f8240400fc47dcd79",
789  "type": "pe_dotnet"
790}"#;
791
792        let image = Annotated::new(DebugImage::PeDotnet(Box::new(NativeDebugImage {
793            debug_id: Annotated::new(
794                "4e2ca887-825e-46f3-968f-25b41ae1b5f3-cc3f6d9e"
795                    .parse()
796                    .unwrap(),
797            ),
798            debug_file: Annotated::new("TimeZoneConverter.pdb".into()),
799            debug_checksum: Annotated::new(
800                "SHA256:87a82c4e5e82f386968f25b41ae1b5f3cc3f6d9e79cfb4464f8240400fc47dcd79".into(),
801            ),
802            ..Default::default()
803        })));
804
805        assert_eq!(image, Annotated::from_json(json).unwrap());
806        assert_eq!(json, image.to_json_pretty().unwrap());
807    }
808
809    #[test]
810    fn test_debug_image_macho_roundtrip() {
811        let json = r#"{
812  "code_id": "67E9247C-814E-392B-A027-DBDE6748FCBF",
813  "code_file": "crash",
814  "debug_id": "67e9247c-814e-392b-a027-dbde6748fcbf",
815  "arch": "arm64",
816  "image_addr": "0x0",
817  "image_size": 4096,
818  "image_vmaddr": "0x8000",
819  "other": "value",
820  "type": "macho"
821}"#;
822
823        let image = Annotated::new(DebugImage::MachO(Box::new(NativeDebugImage {
824            code_id: Annotated::new("67E9247C-814E-392B-A027-DBDE6748FCBF".parse().unwrap()),
825            code_file: Annotated::new("crash".into()),
826            debug_id: Annotated::new("67e9247c-814e-392b-a027-dbde6748fcbf".parse().unwrap()),
827            debug_file: Annotated::empty(),
828            debug_checksum: Annotated::empty(),
829            arch: Annotated::new("arm64".to_string()),
830            image_addr: Annotated::new(Addr(0)),
831            image_size: Annotated::new(4096),
832            image_vmaddr: Annotated::new(Addr(32768)),
833            other: {
834                let mut map = Object::new();
835                map.insert(
836                    "other".to_string(),
837                    Annotated::new(Value::String("value".to_string())),
838                );
839                map
840            },
841        })));
842
843        assert_eq!(image, Annotated::from_json(json).unwrap());
844    }
845
846    #[test]
847    fn test_debug_image_pe_roundtrip() {
848        let json = r#"{
849  "code_id": "59b0d8f3183000",
850  "code_file": "C:\\Windows\\System32\\ntdll.dll",
851  "debug_id": "971f98e5-ce60-41ff-b2d7-235bbeb34578-1",
852  "debug_file": "wntdll.pdb",
853  "arch": "arm64",
854  "image_addr": "0x0",
855  "image_size": 4096,
856  "image_vmaddr": "0x8000",
857  "other": "value",
858  "type": "pe"
859}"#;
860
861        let image = Annotated::new(DebugImage::Pe(Box::new(NativeDebugImage {
862            code_id: Annotated::new("59b0d8f3183000".parse().unwrap()),
863            code_file: Annotated::new("C:\\Windows\\System32\\ntdll.dll".into()),
864            debug_id: Annotated::new("971f98e5-ce60-41ff-b2d7-235bbeb34578-1".parse().unwrap()),
865            debug_file: Annotated::new("wntdll.pdb".into()),
866            debug_checksum: Annotated::empty(),
867            arch: Annotated::new("arm64".to_string()),
868            image_addr: Annotated::new(Addr(0)),
869            image_size: Annotated::new(4096),
870            image_vmaddr: Annotated::new(Addr(32768)),
871            other: {
872                let mut map = Object::new();
873                map.insert(
874                    "other".to_string(),
875                    Annotated::new(Value::String("value".to_string())),
876                );
877                map
878            },
879        })));
880
881        assert_eq!(image, Annotated::from_json(json).unwrap());
882        assert_eq!(json, image.to_json_pretty().unwrap());
883    }
884
885    #[test]
886    fn test_source_map_image_roundtrip() {
887        let json = r#"{
888  "code_file": "https://mycdn.invalid/foo.js.min",
889  "debug_id": "971f98e5-ce60-41ff-b2d7-235bbeb34578",
890  "debug_file": "https://mycdn.invalid/foo.js.map",
891  "other": "value",
892  "type": "sourcemap"
893}"#;
894
895        let image = Annotated::new(DebugImage::SourceMap(Box::new(SourceMapDebugImage {
896            code_file: Annotated::new("https://mycdn.invalid/foo.js.min".into()),
897            debug_file: Annotated::new("https://mycdn.invalid/foo.js.map".into()),
898            debug_id: Annotated::new("971f98e5-ce60-41ff-b2d7-235bbeb34578".parse().unwrap()),
899            other: {
900                let mut map = Object::new();
901                map.insert(
902                    "other".to_string(),
903                    Annotated::new(Value::String("value".to_string())),
904                );
905                map
906            },
907        })));
908
909        assert_eq!(image, Annotated::from_json(json).unwrap());
910        assert_eq!(json, image.to_json_pretty().unwrap());
911    }
912
913    #[test]
914    fn test_debug_image_other_roundtrip() {
915        let json = r#"{"other":"value","type":"mytype"}"#;
916        let image = Annotated::new(DebugImage::Other({
917            let mut map = Map::new();
918            map.insert(
919                "type".to_string(),
920                Annotated::new(Value::String("mytype".to_string())),
921            );
922            map.insert(
923                "other".to_string(),
924                Annotated::new(Value::String("value".to_string())),
925            );
926            map
927        }));
928
929        assert_eq!(image, Annotated::from_json(json).unwrap());
930        assert_eq!(json, image.to_json().unwrap());
931    }
932
933    #[test]
934    fn test_debug_image_untagged_roundtrip() {
935        let json = r#"{"other":"value"}"#;
936        let image = Annotated::new(DebugImage::Other({
937            let mut map = Map::new();
938            map.insert(
939                "other".to_string(),
940                Annotated::new(Value::String("value".to_string())),
941            );
942            map
943        }));
944
945        assert_eq!(image, Annotated::from_json(json).unwrap());
946        assert_eq!(json, image.to_json().unwrap());
947    }
948
949    #[test]
950    fn test_debug_meta_roundtrip() {
951        // NOTE: images are tested separately
952        let json = r#"{
953  "sdk_info": {
954    "sdk_name": "iOS",
955    "version_major": 10,
956    "version_minor": 3,
957    "version_patchlevel": 0,
958    "other": "value"
959  },
960  "other": "value"
961}"#;
962        let meta = Annotated::new(DebugMeta {
963            system_sdk: Annotated::new(SystemSdkInfo {
964                sdk_name: Annotated::new("iOS".to_string()),
965                version_major: Annotated::new(10),
966                version_minor: Annotated::new(3),
967                version_patchlevel: Annotated::new(0),
968                other: {
969                    let mut map = Map::new();
970                    map.insert(
971                        "other".to_string(),
972                        Annotated::new(Value::String("value".to_string())),
973                    );
974                    map
975                },
976            }),
977            other: {
978                let mut map = Map::new();
979                map.insert(
980                    "other".to_string(),
981                    Annotated::new(Value::String("value".to_string())),
982                );
983                map
984            },
985            ..Default::default()
986        });
987
988        assert_eq!(meta, Annotated::from_json(json).unwrap());
989        assert_eq!(json, meta.to_json_pretty().unwrap());
990    }
991
992    #[test]
993    fn test_debug_meta_default_values() {
994        let json = "{}";
995        let meta = Annotated::new(DebugMeta::default());
996
997        assert_eq!(meta, Annotated::from_json(json).unwrap());
998        assert_eq!(json, meta.to_json_pretty().unwrap());
999    }
1000}