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