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