1 extern crate backtrace;
2 
3 use std::os::raw::c_void;
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!(all(unix, feature = "libbacktrace")) &&
9                             !cfg!(target_os = "fuchsia") && !cfg!(target_os = "macos") &&
10                             !cfg!(target_os = "ios");
11 static CORESYMBOLICATION: bool = cfg!(all(any(target_os = "macos", target_os = "ios"),
12                                           feature = "coresymbolication"));
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",
17                                         unix,
18                                         target_os = "linux"));
19 
20 #[test]
21 // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing
22 #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
smoke_test_frames()23 fn smoke_test_frames() {
24     frame_1(line!());
25     #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
26     #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
27     #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
28     #[inline(never)] fn frame_4(start_line: u32) {
29         let mut v = Vec::new();
30         backtrace::trace(|cx| {
31             v.push((cx.ip(), cx.symbol_address()));
32             true
33         });
34 
35         if v.len() < 5 {
36             assert!(!LIBUNWIND);
37             assert!(!UNIX_BACKTRACE);
38             assert!(!DBGHELP);
39             return
40         }
41 
42         // On 32-bit windows apparently the first frame isn't our backtrace
43         // frame but it's actually this frame. I'm not entirely sure why, but at
44         // least it seems consistent?
45         let o = if cfg!(all(windows, target_pointer_width = "32")) {1} else {0};
46         // frame offset 0 is the `backtrace::trace` function, but that's generic
47         assert_frame(&v, o, 1, frame_4 as usize, "frame_4",
48                      "tests/smoke.rs", start_line + 6);
49         assert_frame(&v, o, 2, frame_3 as usize, "frame_3", "tests/smoke.rs",
50                      start_line + 3);
51         assert_frame(&v, o, 3, frame_2 as usize, "frame_2", "tests/smoke.rs",
52                      start_line + 2);
53         assert_frame(&v, o, 4, frame_1 as usize, "frame_1", "tests/smoke.rs",
54                      start_line + 1);
55         assert_frame(&v, o, 5, smoke_test_frames as usize,
56                      "smoke_test_frames", "", 0);
57     }
58 
59     fn assert_frame(syms: &[(*mut c_void, *mut c_void)],
60                     offset: usize,
61                     idx: usize,
62                     actual_fn_pointer: usize,
63                     expected_name: &str,
64                     expected_file: &str,
65                     expected_line: u32) {
66         if offset > idx { return }
67         println!("frame: {}", idx);
68         let (ip, sym) = syms[idx - offset];
69         let ip = ip as usize;
70         let sym = sym as usize;
71         assert!(ip >= sym);
72         assert!(sym >= actual_fn_pointer);
73 
74         // windows dbghelp is *quite* liberal (and wrong) in many of its reports
75         // right now...
76         //
77         // This assertion can also fail for release builds, so skip it there
78         if !DBGHELP && cfg!(debug_assertions) {
79             assert!(sym - actual_fn_pointer < 1024);
80         }
81 
82         let mut resolved = 0;
83         let can_resolve = DLADDR || LIBBACKTRACE || CORESYMBOLICATION || DBGHELP || GIMLI_SYMBOLIZE;
84 
85         let mut name = None;
86         let mut addr = None;
87         let mut line = None;
88         let mut file = None;
89         backtrace::resolve(ip as *mut c_void, |sym| {
90             resolved += 1;
91             name = sym.name().map(|v| v.to_string());
92             addr = sym.addr();
93             line = sym.lineno();
94             file = sym.filename().map(|v| v.to_path_buf());
95             println!("  sym: {:?}", name);
96         });
97 
98         // dbghelp doesn't always resolve symbols right now
99         match resolved {
100             0 => return assert!(!can_resolve || DBGHELP),
101             _ => {}
102         }
103 
104         // * linux dladdr doesn't work (only consults local symbol table)
105         // * windows dbghelp isn't great for GNU
106         if can_resolve &&
107            !(cfg!(target_os = "linux") && DLADDR) &&
108            !(DBGHELP && !MSVC)
109         {
110             let name = name.expect("didn't find a name");
111 
112             // in release mode names get weird as functions can get merged
113             // together with `mergefunc`, so only assert this in debug mode
114             if cfg!(debug_assertions) {
115                 assert!(name.contains(expected_name),
116                         "didn't find `{}` in `{}`", expected_name, name);
117             }
118         }
119 
120         if can_resolve {
121             addr.expect("didn't find a symbol");
122         }
123 
124         if (LIBBACKTRACE || CORESYMBOLICATION || (DBGHELP && MSVC)) && cfg!(debug_assertions) {
125             let line = line.expect("didn't find a line number");
126             let file = file.expect("didn't find a line number");
127             if !expected_file.is_empty() {
128                 assert!(file.ends_with(expected_file),
129                         "{:?} didn't end with {:?}", file, expected_file);
130             }
131             if expected_line != 0 {
132                 assert!(line == expected_line,
133                         "bad line number on frame for `{}`: {} != {}",
134                         expected_name, line, expected_line);
135             }
136         }
137     }
138 }
139 
140 #[test]
many_threads()141 fn many_threads() {
142     let threads = (0..16).map(|_| {
143         thread::spawn(|| {
144             for _ in 0..16 {
145                 backtrace::trace(|frame| {
146                     backtrace::resolve(frame.ip(), |symbol| {
147                         let _s = symbol.name().map(|s| s.to_string());
148                     });
149                     true
150                 });
151             }
152         })
153     }).collect::<Vec<_>>();
154 
155     for t in threads {
156         t.join().unwrap()
157     }
158 }
159 
160 #[test]
161 #[cfg(feature = "rustc-serialize")]
is_rustc_serialize()162 fn is_rustc_serialize() {
163     extern crate rustc_serialize;
164 
165     fn is_encode<T: rustc_serialize::Encodable>() {}
166     fn is_decode<T: rustc_serialize::Decodable>() {}
167 
168     is_encode::<backtrace::Backtrace>();
169     is_decode::<backtrace::Backtrace>();
170 }
171 
172 #[test]
173 #[cfg(feature = "serde")]
is_serde()174 fn is_serde() {
175     extern crate serde;
176 
177     fn is_serialize<T: serde::ser::Serialize>() {}
178     fn is_deserialize<T: serde::de::DeserializeOwned>() {}
179 
180     is_serialize::<backtrace::Backtrace>();
181     is_deserialize::<backtrace::Backtrace>();
182 }
183