1 use backtrace::Frame;
2 use std::thread;
3 
4 // Reflects the conditional compilation logic at end of src/symbolize/mod.rs
5 static NOOP: bool = false;
6 static DBGHELP: bool = !NOOP
7     && cfg!(all(
8         windows,
9         target_env = "msvc",
10         not(target_vendor = "uwp")
11     ));
12 static LIBBACKTRACE: bool = !NOOP
13     && !DBGHELP
14     && cfg!(all(
15         feature = "libbacktrace",
16         any(
17             unix,
18             all(windows, not(target_vendor = "uwp"), target_env = "gnu")
19         ),
20         not(target_os = "fuchsia"),
21         not(target_os = "emscripten"),
22         not(target_env = "uclibc"),
23         not(target_env = "libnx"),
24     ));
25 static GIMLI_SYMBOLIZE: bool = !NOOP
26     && !DBGHELP
27     && !LIBBACKTRACE
28     && cfg!(all(
29         feature = "gimli-symbolize",
30         any(unix, windows),
31         not(target_vendor = "uwp"),
32         not(target_os = "emscripten"),
33     ));
34 static MIRI_SYMBOLIZE: bool = cfg!(miri);
35 
36 #[test]
37 // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
38 #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
39 #[rustfmt::skip] // we care about line numbers here
smoke_test_frames()40 fn smoke_test_frames() {
41     frame_1(line!());
42     #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
43     #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
44     #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
45     #[inline(never)] fn frame_4(start_line: u32) {
46         let mut v = Vec::new();
47         backtrace::trace(|cx| {
48             v.push(cx.clone());
49             true
50         });
51 
52         // Various platforms have various bits of weirdness about their
53         // backtraces. To find a good starting spot let's search through the
54         // frames
55         let target = frame_4 as usize;
56         let offset = v
57             .iter()
58             .map(|frame| frame.symbol_address() as usize)
59             .enumerate()
60             .filter_map(|(i, sym)| {
61                 if sym >= target {
62                     Some((sym, i))
63                 } else {
64                     None
65                 }
66             })
67             .min()
68             .unwrap()
69             .1;
70         let mut frames = v[offset..].iter();
71 
72         assert_frame(
73             frames.next().unwrap(),
74             frame_4 as usize,
75             "frame_4",
76             "tests/smoke.rs",
77             start_line + 6,
78             9,
79         );
80         assert_frame(
81             frames.next().unwrap(),
82             frame_3 as usize,
83             "frame_3",
84             "tests/smoke.rs",
85             start_line + 3,
86             52,
87         );
88         assert_frame(
89             frames.next().unwrap(),
90             frame_2 as usize,
91             "frame_2",
92             "tests/smoke.rs",
93             start_line + 2,
94             52,
95         );
96         assert_frame(
97             frames.next().unwrap(),
98             frame_1 as usize,
99             "frame_1",
100             "tests/smoke.rs",
101             start_line + 1,
102             52,
103         );
104         assert_frame(
105             frames.next().unwrap(),
106             smoke_test_frames as usize,
107             "smoke_test_frames",
108             "",
109             0,
110             0,
111         );
112     }
113 
114     fn assert_frame(
115         frame: &Frame,
116         actual_fn_pointer: usize,
117         expected_name: &str,
118         expected_file: &str,
119         expected_line: u32,
120         expected_col: u32,
121     ) {
122         backtrace::resolve_frame(frame, |sym| {
123             print!("symbol  ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address());
124             if let Some(name) = sym.name() {
125                 print!("name:{} ", name);
126             }
127             if let Some(file) = sym.filename() {
128                 print!("file:{} ", file.display());
129             }
130             if let Some(lineno) = sym.lineno() {
131                 print!("lineno:{} ", lineno);
132             }
133             if let Some(colno) = sym.colno() {
134                 print!("colno:{} ", colno);
135             }
136             println!();
137         });
138 
139         let ip = frame.ip() as usize;
140         let sym = frame.symbol_address() as usize;
141         assert!(ip >= sym);
142         assert!(
143             sym >= actual_fn_pointer,
144             "{:?} < {:?} ({} {}:{}:{})",
145             sym as *const usize,
146             actual_fn_pointer as *const usize,
147             expected_name,
148             expected_file,
149             expected_line,
150             expected_col,
151         );
152 
153         // windows dbghelp is *quite* liberal (and wrong) in many of its reports
154         // right now...
155         //
156         // This assertion can also fail for release builds, so skip it there
157         if cfg!(debug_assertions) {
158             assert!(sym - actual_fn_pointer < 1024);
159         }
160 
161         let mut resolved = 0;
162         let can_resolve = LIBBACKTRACE || GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE;
163         let can_resolve_cols = GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE;
164 
165         let mut name = None;
166         let mut addr = None;
167         let mut col  = None;
168         let mut line = None;
169         let mut file = None;
170         backtrace::resolve_frame(frame, |sym| {
171             resolved += 1;
172             name = sym.name().map(|v| v.to_string());
173             addr = sym.addr();
174             col  = sym.colno();
175             line = sym.lineno();
176             file = sym.filename().map(|v| v.to_path_buf());
177         });
178 
179         // dbghelp doesn't always resolve symbols right now
180         match resolved {
181             0 => return assert!(!can_resolve),
182             _ => {}
183         }
184 
185         if can_resolve {
186             let name = name.expect("didn't find a name");
187 
188             // in release mode names get weird as functions can get merged
189             // together with `mergefunc`, so only assert this in debug mode
190             if cfg!(debug_assertions) {
191                 assert!(
192                     name.contains(expected_name),
193                     "didn't find `{}` in `{}`",
194                     expected_name,
195                     name
196                 );
197             }
198         }
199 
200         if can_resolve {
201             addr.expect("didn't find a symbol");
202         }
203 
204         if cfg!(debug_assertions) {
205             let line = line.expect("didn't find a line number");
206             let file = file.expect("didn't find a line number");
207             if !expected_file.is_empty() {
208                 assert!(
209                     file.ends_with(expected_file),
210                     "{:?} didn't end with {:?}",
211                     file,
212                     expected_file
213                 );
214             }
215             if expected_line != 0 {
216                 assert!(
217                     line == expected_line,
218                     "bad line number on frame for `{}`: {} != {}",
219                     expected_name,
220                     line,
221                     expected_line
222                 );
223             }
224             if can_resolve_cols {
225                 let col = col.expect("didn't find a column number");
226                 if expected_col != 0 {
227                     assert!(
228                         col == expected_col,
229                         "bad column number on frame for `{}`: {} != {}",
230                         expected_name,
231                         col,
232                         expected_col
233                     );
234                 }
235             }
236         }
237     }
238 }
239 
240 #[test]
many_threads()241 fn many_threads() {
242     let threads = (0..16)
243         .map(|_| {
244             thread::spawn(|| {
245                 for _ in 0..16 {
246                     backtrace::trace(|frame| {
247                         backtrace::resolve(frame.ip(), |symbol| {
248                             let _s = symbol.name().map(|s| s.to_string());
249                         });
250                         true
251                     });
252                 }
253             })
254         })
255         .collect::<Vec<_>>();
256 
257     for t in threads {
258         t.join().unwrap()
259     }
260 }
261 
262 #[test]
263 #[cfg(feature = "rustc-serialize")]
is_rustc_serialize()264 fn is_rustc_serialize() {
265     extern crate rustc_serialize;
266 
267     fn is_encode<T: rustc_serialize::Encodable>() {}
268     fn is_decode<T: rustc_serialize::Decodable>() {}
269 
270     is_encode::<backtrace::Backtrace>();
271     is_decode::<backtrace::Backtrace>();
272 }
273 
274 #[test]
275 #[cfg(feature = "serde")]
is_serde()276 fn is_serde() {
277     extern crate serde;
278 
279     fn is_serialize<T: serde::ser::Serialize>() {}
280     fn is_deserialize<T: serde::de::DeserializeOwned>() {}
281 
282     is_serialize::<backtrace::Backtrace>();
283     is_deserialize::<backtrace::Backtrace>();
284 }
285 
286 #[test]
sp_smoke_test()287 fn sp_smoke_test() {
288     let mut refs = vec![];
289     recursive_stack_references(&mut refs);
290     return;
291 
292     #[inline(never)]
293     fn recursive_stack_references(refs: &mut Vec<usize>) {
294         assert!(refs.len() < 5);
295 
296         let x = refs.len();
297         refs.push(&x as *const _ as usize);
298 
299         if refs.len() < 5 {
300             recursive_stack_references(refs);
301             eprintln!("exiting: {}", x);
302             return;
303         }
304 
305         backtrace::trace(make_trace_closure(refs));
306         eprintln!("exiting: {}", x);
307     }
308 
309     // NB: the following `make_*` functions are pulled out of line, rather than
310     // defining their results as inline closures at their call sites, so that
311     // the resulting closures don't have "recursive_stack_references" in their
312     // mangled names.
313 
314     fn make_trace_closure<'a>(
315         refs: &'a mut Vec<usize>,
316     ) -> impl FnMut(&backtrace::Frame) -> bool + 'a {
317         let mut child_sp = None;
318         let mut child_ref = None;
319         move |frame| {
320             eprintln!("\n=== frame ===================================");
321 
322             let mut is_recursive_stack_references = false;
323             backtrace::resolve(frame.ip(), |sym| {
324                 is_recursive_stack_references |= (LIBBACKTRACE || GIMLI_SYMBOLIZE)
325                     && sym
326                         .name()
327                         .and_then(|name| name.as_str())
328                         .map_or(false, |name| {
329                             eprintln!("name = {}", name);
330                             name.contains("recursive_stack_references")
331                         })
332             });
333 
334             let sp = frame.sp() as usize;
335             eprintln!("sp  = {:p}", sp as *const u8);
336             if sp == 0 {
337                 // If the SP is null, then we don't have an implementation for
338                 // getting the SP on this target. Just keep walking the stack,
339                 // but don't make our assertions about the on-stack pointers and
340                 // SP values.
341                 return true;
342             }
343 
344             // The stack grows down.
345             if let Some(child_sp) = child_sp {
346                 assert!(child_sp <= sp);
347             }
348 
349             if is_recursive_stack_references {
350                 let r = refs.pop().unwrap();
351                 eprintln!("ref = {:p}", r as *const u8);
352                 if sp != 0 {
353                     assert!(r > sp);
354                     if let Some(child_ref) = child_ref {
355                         assert!(sp >= child_ref);
356                     }
357                 }
358                 child_ref = Some(r);
359             }
360 
361             child_sp = Some(sp);
362             true
363         }
364     }
365 }
366