relay_event_normalization/
stacktrace.rs

1use std::mem;
2
3use relay_event_schema::processor;
4use relay_event_schema::protocol::{Frame, RawStacktrace};
5use relay_protocol::{Annotated, Empty, Meta};
6use url::Url;
7
8fn is_url(filename: &str) -> bool {
9    filename.starts_with("file:")
10        || filename.starts_with("http:")
11        || filename.starts_with("https:")
12        || filename.starts_with("applewebdata:")
13}
14
15pub fn normalize_stacktrace(stacktrace: &mut RawStacktrace, _meta: &mut Meta) {
16    // This processing is only done for non raw frames (i.e. not for exception.raw_stacktrace).
17    if let Some(frames) = stacktrace.frames.value_mut() {
18        for frame in frames.iter_mut() {
19            normalize_non_raw_frame(frame);
20        }
21    }
22}
23
24pub fn normalize_non_raw_frame(frame: &mut Annotated<Frame>) {
25    let _ = processor::apply(frame, |frame, _meta| {
26        if frame.abs_path.value().is_empty() {
27            frame.abs_path = mem::replace(&mut frame.filename, Annotated::empty());
28        }
29
30        if frame.filename.value().is_empty() {
31            if let Some(abs_path) = frame.abs_path.value_mut() {
32                frame.filename = Annotated::new(abs_path.clone());
33
34                if is_url(abs_path.as_str()) {
35                    if let Ok(url) = Url::parse(abs_path.as_str()) {
36                        let path = url.path();
37
38                        if !path.is_empty() && path != "/" {
39                            frame.filename = Annotated::new(path.into());
40                        }
41                    }
42                }
43            }
44        }
45
46        if frame.function.as_str() == Some("?") {
47            frame.function.set_value(None);
48        }
49
50        if frame.symbol.as_str() == Some("?") {
51            frame.symbol.set_value(None);
52        }
53
54        if let Some(lines) = frame.pre_context.value_mut() {
55            for line in lines.iter_mut() {
56                line.get_or_insert_with(String::new);
57            }
58        }
59
60        if let Some(lines) = frame.post_context.value_mut() {
61            for line in lines.iter_mut() {
62                line.get_or_insert_with(String::new);
63            }
64        }
65
66        if frame.context_line.value().is_none()
67            && (!frame.pre_context.is_empty() || !frame.post_context.is_empty())
68        {
69            frame.context_line.set_value(Some(String::new()));
70        }
71
72        Ok(())
73    });
74}
75
76#[cfg(test)]
77mod tests {
78    use similar_asserts::assert_eq;
79
80    use super::*;
81
82    #[test]
83    fn test_coerces_url_filenames() {
84        let mut frame = Annotated::new(Frame {
85            lineno: Annotated::new(1),
86            filename: Annotated::new("http://foo.com/foo.js".into()),
87            ..Default::default()
88        });
89
90        normalize_non_raw_frame(&mut frame);
91        let frame = frame.value().unwrap();
92
93        assert_eq!(frame.filename.value().unwrap().as_str(), "/foo.js");
94        assert_eq!(
95            frame.abs_path.value().unwrap().as_str(),
96            "http://foo.com/foo.js"
97        );
98    }
99
100    #[test]
101    fn test_does_not_overwrite_filename() {
102        let mut frame = Annotated::new(Frame {
103            lineno: Annotated::new(1),
104            filename: Annotated::new("foo.js".into()),
105            abs_path: Annotated::new("http://foo.com/foo.js".into()),
106            ..Default::default()
107        });
108
109        normalize_non_raw_frame(&mut frame);
110        let frame = frame.value().unwrap();
111
112        assert_eq!(frame.filename.value().unwrap().as_str(), "foo.js");
113        assert_eq!(
114            frame.abs_path.value().unwrap().as_str(),
115            "http://foo.com/foo.js"
116        );
117    }
118
119    #[test]
120    fn test_ignores_results_with_empty_path() {
121        let mut frame = Annotated::new(Frame {
122            lineno: Annotated::new(1),
123            abs_path: Annotated::new("http://foo.com".into()),
124            ..Default::default()
125        });
126
127        normalize_non_raw_frame(&mut frame);
128        let frame = frame.value().unwrap();
129
130        assert_eq!(frame.filename.value().unwrap().as_str(), "http://foo.com");
131        assert_eq!(
132            frame.abs_path.value().unwrap().as_str(),
133            frame.filename.value().unwrap().as_str()
134        );
135    }
136
137    #[test]
138    fn test_ignores_results_with_slash_path() {
139        let mut frame = Annotated::new(Frame {
140            lineno: Annotated::new(1),
141            abs_path: Annotated::new("http://foo.com/".into()),
142            ..Default::default()
143        });
144
145        normalize_non_raw_frame(&mut frame);
146        let frame = frame.value().unwrap();
147
148        assert_eq!(frame.filename.value().unwrap().as_str(), "http://foo.com/");
149        assert_eq!(
150            frame.abs_path.value().unwrap().as_str(),
151            frame.filename.value().unwrap().as_str()
152        );
153    }
154
155    #[test]
156    fn test_coerce_empty_filename() {
157        let mut frame = Annotated::new(Frame {
158            lineno: Annotated::new(1),
159            filename: Annotated::new("".into()),
160            abs_path: Annotated::new("http://foo.com/foo.js".into()),
161            ..Default::default()
162        });
163
164        normalize_non_raw_frame(&mut frame);
165        let frame = frame.value().unwrap();
166
167        assert_eq!(frame.filename.value().unwrap().as_str(), "/foo.js");
168        assert_eq!(
169            frame.abs_path.value().unwrap().as_str(),
170            "http://foo.com/foo.js"
171        );
172    }
173
174    #[test]
175    fn test_is_url() {
176        assert!(is_url("http://example.org/"));
177        assert!(is_url("https://example.org/"));
178        assert!(is_url("file:///tmp/filename"));
179        assert!(is_url(
180            "applewebdata://00000000-0000-1000-8080-808080808080"
181        ));
182        assert!(!is_url("app:///index.bundle")); // react native
183        assert!(!is_url("webpack:///./app/index.jsx")); // webpack bundle
184        assert!(!is_url("data:,"));
185        assert!(!is_url("blob:\x00"));
186    }
187}