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