1 extern crate backtrace;
2 
3 use backtrace::Frame;
4 use std::thread;
5 
6 static LIBUNWIND: bool = cfg!(all(unix, feature = "libunwind"));
7 static UNIX_BACKTRACE: bool = cfg!(all(unix, feature = "unix-backtrace"));
8 static LIBBACKTRACE: bool = cfg!(feature = "libbacktrace") && !cfg!(target_os = "fuchsia");
9 static CORESYMBOLICATION: bool = cfg!(all(
10     any(target_os = "macos", target_os = "ios"),
11     feature = "coresymbolication"
12 ));
13 static DLADDR: bool = cfg!(all(unix, feature = "dladdr")) && !cfg!(target_os = "fuchsia");
14 static DBGHELP: bool = cfg!(all(windows, feature = "dbghelp"));
15 static MSVC: bool = cfg!(target_env = "msvc");
16 static GIMLI_SYMBOLIZE: bool = cfg!(all(feature = "gimli-symbolize", unix, target_os = "linux"));
17 
18 #[test]
19 // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
20 #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
21 #[rustfmt::skip] // we care about line numbers here
smoke_test_frames()22 fn smoke_test_frames() {
23     frame_1(line!());
24     #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
25     #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
26     #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
27     #[inline(never)] fn frame_4(start_line: u32) {
28         let mut v = Vec::new();
29         backtrace::trace(|cx| {
30             v.push(cx.clone());
31             true
32         });
33 
34         if v.len() < 5 {
35             assert!(!LIBUNWIND);
36             assert!(!UNIX_BACKTRACE);
37             assert!(!DBGHELP);
38             return;
39         }
40 
41         // Various platforms have various bits of weirdness about their
42         // backtraces. To find a good starting spot let's search through the
43         // frames
44         let target = frame_4 as usize;
45         let offset = v
46             .iter()
47             .map(|frame| frame.symbol_address() as usize)
48             .enumerate()
49             .filter_map(|(i, sym)| {
50                 if sym >= target {
51                     Some((sym, i))
52                 } else {
53                     None
54                 }
55             })
56             .min()
57             .unwrap()
58             .1;
59         let mut frames = v[offset..].iter();
60 
61         assert_frame(
62             frames.next().unwrap(),
63             frame_4 as usize,
64             "frame_4",
65             "tests/smoke.rs",
66             start_line + 6,
67         );
68         assert_frame(
69             frames.next().unwrap(),
70             frame_3 as usize,
71             "frame_3",
72             "tests/smoke.rs",
73             start_line + 3,
74         );
75         assert_frame(
76             frames.next().unwrap(),
77             frame_2 as usize,
78             "frame_2",
79             "tests/smoke.rs",
80             start_line + 2,
81         );
82         assert_frame(
83             frames.next().unwrap(),
84             frame_1 as usize,
85             "frame_1",
86             "tests/smoke.rs",
87             start_line + 1,
88         );
89         assert_frame(
90             frames.next().unwrap(),
91             smoke_test_frames as usize,
92             "smoke_test_frames",
93             "",
94             0,
95         );
96     }
97 
98     fn assert_frame(
99         frame: &Frame,
100         actual_fn_pointer: usize,
101         expected_name: &str,
102         expected_file: &str,
103         expected_line: u32,
104     ) {
105         backtrace::resolve_frame(frame, |sym| {
106             print!("symbol  ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address());
107             if let Some(name) = sym.name() {
108                 print!("name:{} ", name);
109             }
110             if let Some(file) = sym.filename() {
111                 print!("file:{} ", file.display());
112             }
113             if let Some(lineno) = sym.lineno() {
114                 print!("lineno:{} ", lineno);
115             }
116             println!();
117         });
118 
119         let ip = frame.ip() as usize;
120         let sym = frame.symbol_address() as usize;
121         assert!(ip >= sym);
122         assert!(
123             sym >= actual_fn_pointer,
124             "{:?} < {:?} ({} {}:{})",
125             sym as *const usize,
126             actual_fn_pointer as *const usize,
127             expected_name,
128             expected_file,
129             expected_line,
130         );
131 
132         // windows dbghelp is *quite* liberal (and wrong) in many of its reports
133         // right now...
134         //
135         // This assertion can also fail for release builds, so skip it there
136         if !DBGHELP && cfg!(debug_assertions) {
137             assert!(sym - actual_fn_pointer < 1024);
138         }
139 
140         let mut resolved = 0;
141         let can_resolve = DLADDR || LIBBACKTRACE || CORESYMBOLICATION || DBGHELP || GIMLI_SYMBOLIZE;
142 
143         let mut name = None;
144         let mut addr = None;
145         let mut line = None;
146         let mut file = None;
147         backtrace::resolve_frame(frame, |sym| {
148             resolved += 1;
149             name = sym.name().map(|v| v.to_string());
150             addr = sym.addr();
151             line = sym.lineno();
152             file = sym.filename().map(|v| v.to_path_buf());
153         });
154 
155         // dbghelp doesn't always resolve symbols right now
156         match resolved {
157             0 => return assert!(!can_resolve || DBGHELP),
158             _ => {}
159         }
160 
161         // * linux dladdr doesn't work (only consults local symbol table)
162         // * windows dbghelp isn't great for GNU
163         if can_resolve && !(cfg!(target_os = "linux") && DLADDR) && !(DBGHELP && !MSVC) {
164             let name = name.expect("didn't find a name");
165 
166             // in release mode names get weird as functions can get merged
167             // together with `mergefunc`, so only assert this in debug mode
168             if cfg!(debug_assertions) {
169                 assert!(
170                     name.contains(expected_name),
171                     "didn't find `{}` in `{}`",
172                     expected_name,
173                     name
174                 );
175             }
176         }
177 
178         if can_resolve {
179             addr.expect("didn't find a symbol");
180         }
181 
182         if (LIBBACKTRACE || CORESYMBOLICATION || (DBGHELP && MSVC)) && cfg!(debug_assertions) {
183             let line = line.expect("didn't find a line number");
184             let file = file.expect("didn't find a line number");
185             if !expected_file.is_empty() {
186                 assert!(
187                     file.ends_with(expected_file),
188                     "{:?} didn't end with {:?}",
189                     file,
190                     expected_file
191                 );
192             }
193             if expected_line != 0 {
194                 assert!(
195                     line == expected_line,
196                     "bad line number on frame for `{}`: {} != {}",
197                     expected_name,
198                     line,
199                     expected_line
200                 );
201             }
202         }
203     }
204 }
205 
206 #[test]
many_threads()207 fn many_threads() {
208     let threads = (0..16)
209         .map(|_| {
210             thread::spawn(|| {
211                 for _ in 0..16 {
212                     backtrace::trace(|frame| {
213                         backtrace::resolve(frame.ip(), |symbol| {
214                             let _s = symbol.name().map(|s| s.to_string());
215                         });
216                         true
217                     });
218                 }
219             })
220         })
221         .collect::<Vec<_>>();
222 
223     for t in threads {
224         t.join().unwrap()
225     }
226 }
227 
228 #[test]
229 #[cfg(feature = "rustc-serialize")]
is_rustc_serialize()230 fn is_rustc_serialize() {
231     extern crate rustc_serialize;
232 
233     fn is_encode<T: rustc_serialize::Encodable>() {}
234     fn is_decode<T: rustc_serialize::Decodable>() {}
235 
236     is_encode::<backtrace::Backtrace>();
237     is_decode::<backtrace::Backtrace>();
238 }
239 
240 #[test]
241 #[cfg(feature = "serde")]
is_serde()242 fn is_serde() {
243     extern crate serde;
244 
245     fn is_serialize<T: serde::ser::Serialize>() {}
246     fn is_deserialize<T: serde::de::DeserializeOwned>() {}
247 
248     is_serialize::<backtrace::Backtrace>();
249     is_deserialize::<backtrace::Backtrace>();
250 }
251