]>
Commit | Line | Data |
---|---|---|
dc9dc135 | 1 | use backtrace::Frame; |
7cac9316 XL |
2 | use std::thread; |
3 | ||
29967ef6 XL |
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); | |
7cac9316 XL |
35 | |
36 | #[test] | |
8faf50e0 XL |
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)] | |
dc9dc135 | 39 | #[rustfmt::skip] // we care about line numbers here |
7cac9316 XL |
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| { | |
dc9dc135 | 48 | v.push(cx.clone()); |
7cac9316 XL |
49 | true |
50 | }); | |
51 | ||
dc9dc135 XL |
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, | |
29967ef6 | 78 | 9, |
dc9dc135 XL |
79 | ); |
80 | assert_frame( | |
81 | frames.next().unwrap(), | |
82 | frame_3 as usize, | |
83 | "frame_3", | |
84 | "tests/smoke.rs", | |
85 | start_line + 3, | |
29967ef6 | 86 | 52, |
dc9dc135 XL |
87 | ); |
88 | assert_frame( | |
89 | frames.next().unwrap(), | |
90 | frame_2 as usize, | |
91 | "frame_2", | |
92 | "tests/smoke.rs", | |
93 | start_line + 2, | |
29967ef6 | 94 | 52, |
dc9dc135 XL |
95 | ); |
96 | assert_frame( | |
97 | frames.next().unwrap(), | |
98 | frame_1 as usize, | |
99 | "frame_1", | |
100 | "tests/smoke.rs", | |
101 | start_line + 1, | |
29967ef6 | 102 | 52, |
dc9dc135 XL |
103 | ); |
104 | assert_frame( | |
105 | frames.next().unwrap(), | |
106 | smoke_test_frames as usize, | |
107 | "smoke_test_frames", | |
108 | "", | |
109 | 0, | |
29967ef6 | 110 | 0, |
dc9dc135 | 111 | ); |
7cac9316 XL |
112 | } |
113 | ||
dc9dc135 XL |
114 | fn assert_frame( |
115 | frame: &Frame, | |
116 | actual_fn_pointer: usize, | |
117 | expected_name: &str, | |
118 | expected_file: &str, | |
119 | expected_line: u32, | |
29967ef6 | 120 | expected_col: u32, |
dc9dc135 XL |
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 | } | |
29967ef6 XL |
133 | if let Some(colno) = sym.colno() { |
134 | print!("colno:{} ", colno); | |
135 | } | |
dc9dc135 XL |
136 | println!(); |
137 | }); | |
138 | ||
139 | let ip = frame.ip() as usize; | |
140 | let sym = frame.symbol_address() as usize; | |
7cac9316 | 141 | assert!(ip >= sym); |
dc9dc135 XL |
142 | assert!( |
143 | sym >= actual_fn_pointer, | |
29967ef6 | 144 | "{:?} < {:?} ({} {}:{}:{})", |
dc9dc135 XL |
145 | sym as *const usize, |
146 | actual_fn_pointer as *const usize, | |
147 | expected_name, | |
148 | expected_file, | |
149 | expected_line, | |
29967ef6 | 150 | expected_col, |
dc9dc135 | 151 | ); |
7cac9316 XL |
152 | |
153 | // windows dbghelp is *quite* liberal (and wrong) in many of its reports | |
154 | // right now... | |
0731742a XL |
155 | // |
156 | // This assertion can also fail for release builds, so skip it there | |
f035d41b | 157 | if cfg!(debug_assertions) { |
7cac9316 XL |
158 | assert!(sym - actual_fn_pointer < 1024); |
159 | } | |
160 | ||
161 | let mut resolved = 0; | |
29967ef6 XL |
162 | let can_resolve = LIBBACKTRACE || GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE; |
163 | let can_resolve_cols = GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE; | |
7cac9316 XL |
164 | |
165 | let mut name = None; | |
166 | let mut addr = None; | |
29967ef6 | 167 | let mut col = None; |
7cac9316 XL |
168 | let mut line = None; |
169 | let mut file = None; | |
dc9dc135 | 170 | backtrace::resolve_frame(frame, |sym| { |
7cac9316 XL |
171 | resolved += 1; |
172 | name = sym.name().map(|v| v.to_string()); | |
173 | addr = sym.addr(); | |
29967ef6 | 174 | col = sym.colno(); |
7cac9316 XL |
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 { | |
f035d41b | 181 | 0 => return assert!(!can_resolve), |
7cac9316 XL |
182 | _ => {} |
183 | } | |
184 | ||
f035d41b | 185 | if can_resolve { |
7cac9316 | 186 | let name = name.expect("didn't find a name"); |
0731742a XL |
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) { | |
dc9dc135 XL |
191 | assert!( |
192 | name.contains(expected_name), | |
193 | "didn't find `{}` in `{}`", | |
194 | expected_name, | |
195 | name | |
196 | ); | |
0731742a | 197 | } |
7cac9316 XL |
198 | } |
199 | ||
200 | if can_resolve { | |
201 | addr.expect("didn't find a symbol"); | |
202 | } | |
203 | ||
f035d41b | 204 | if cfg!(debug_assertions) { |
7cac9316 XL |
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() { | |
dc9dc135 XL |
208 | assert!( |
209 | file.ends_with(expected_file), | |
210 | "{:?} didn't end with {:?}", | |
211 | file, | |
212 | expected_file | |
213 | ); | |
7cac9316 XL |
214 | } |
215 | if expected_line != 0 { | |
dc9dc135 XL |
216 | assert!( |
217 | line == expected_line, | |
218 | "bad line number on frame for `{}`: {} != {}", | |
219 | expected_name, | |
220 | line, | |
221 | expected_line | |
222 | ); | |
7cac9316 | 223 | } |
29967ef6 XL |
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 | } | |
7cac9316 XL |
236 | } |
237 | } | |
238 | } | |
239 | ||
240 | #[test] | |
241 | fn many_threads() { | |
dc9dc135 XL |
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 | |
7cac9316 | 251 | }); |
dc9dc135 XL |
252 | } |
253 | }) | |
7cac9316 | 254 | }) |
dc9dc135 | 255 | .collect::<Vec<_>>(); |
7cac9316 XL |
256 | |
257 | for t in threads { | |
258 | t.join().unwrap() | |
259 | } | |
260 | } | |
261 | ||
262 | #[test] | |
263 | #[cfg(feature = "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")] | |
276 | fn is_serde() { | |
277 | extern crate serde; | |
278 | ||
279 | fn is_serialize<T: serde::ser::Serialize>() {} | |
041b39d2 | 280 | fn is_deserialize<T: serde::de::DeserializeOwned>() {} |
7cac9316 XL |
281 | |
282 | is_serialize::<backtrace::Backtrace>(); | |
283 | is_deserialize::<backtrace::Backtrace>(); | |
284 | } | |
f035d41b XL |
285 | |
286 | #[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 | } |