use backtrace::Frame; use std::thread; // Reflects the conditional compilation logic at end of src/symbolize/mod.rs static NOOP: bool = false; static DBGHELP: bool = !NOOP && cfg!(all( windows, target_env = "msvc", not(target_vendor = "uwp") )); static LIBBACKTRACE: bool = !NOOP && !DBGHELP && cfg!(all( feature = "libbacktrace", any( unix, all(windows, not(target_vendor = "uwp"), target_env = "gnu") ), not(target_os = "fuchsia"), not(target_os = "emscripten"), not(target_env = "uclibc"), not(target_env = "libnx"), )); static GIMLI_SYMBOLIZE: bool = !NOOP && !DBGHELP && !LIBBACKTRACE && cfg!(all( feature = "gimli-symbolize", any(unix, windows), not(target_vendor = "uwp"), not(target_os = "emscripten"), )); static MIRI_SYMBOLIZE: bool = cfg!(miri); #[test] // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] #[rustfmt::skip] // we care about line numbers here fn smoke_test_frames() { frame_1(line!()); #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) } #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) } #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) } #[inline(never)] fn frame_4(start_line: u32) { let mut v = Vec::new(); backtrace::trace(|cx| { v.push(cx.clone()); true }); // Various platforms have various bits of weirdness about their // backtraces. To find a good starting spot let's search through the // frames let target = frame_4 as usize; let offset = v .iter() .map(|frame| frame.symbol_address() as usize) .enumerate() .filter_map(|(i, sym)| { if sym >= target { Some((sym, i)) } else { None } }) .min() .unwrap() .1; let mut frames = v[offset..].iter(); assert_frame( frames.next().unwrap(), frame_4 as usize, "frame_4", "tests/smoke.rs", start_line + 6, 9, ); assert_frame( frames.next().unwrap(), frame_3 as usize, "frame_3", "tests/smoke.rs", start_line + 3, 52, ); assert_frame( frames.next().unwrap(), frame_2 as usize, "frame_2", "tests/smoke.rs", start_line + 2, 52, ); assert_frame( frames.next().unwrap(), frame_1 as usize, "frame_1", "tests/smoke.rs", start_line + 1, 52, ); assert_frame( frames.next().unwrap(), smoke_test_frames as usize, "smoke_test_frames", "", 0, 0, ); } fn assert_frame( frame: &Frame, actual_fn_pointer: usize, expected_name: &str, expected_file: &str, expected_line: u32, expected_col: u32, ) { backtrace::resolve_frame(frame, |sym| { print!("symbol ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address()); if let Some(name) = sym.name() { print!("name:{} ", name); } if let Some(file) = sym.filename() { print!("file:{} ", file.display()); } if let Some(lineno) = sym.lineno() { print!("lineno:{} ", lineno); } if let Some(colno) = sym.colno() { print!("colno:{} ", colno); } println!(); }); let ip = frame.ip() as usize; let sym = frame.symbol_address() as usize; assert!(ip >= sym); assert!( sym >= actual_fn_pointer, "{:?} < {:?} ({} {}:{}:{})", sym as *const usize, actual_fn_pointer as *const usize, expected_name, expected_file, expected_line, expected_col, ); // windows dbghelp is *quite* liberal (and wrong) in many of its reports // right now... // // This assertion can also fail for release builds, so skip it there if cfg!(debug_assertions) { assert!(sym - actual_fn_pointer < 1024); } let mut resolved = 0; let can_resolve = LIBBACKTRACE || GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE; let can_resolve_cols = GIMLI_SYMBOLIZE || MIRI_SYMBOLIZE; let mut name = None; let mut addr = None; let mut col = None; let mut line = None; let mut file = None; backtrace::resolve_frame(frame, |sym| { resolved += 1; name = sym.name().map(|v| v.to_string()); addr = sym.addr(); col = sym.colno(); line = sym.lineno(); file = sym.filename().map(|v| v.to_path_buf()); }); // dbghelp doesn't always resolve symbols right now match resolved { 0 => return assert!(!can_resolve), _ => {} } if can_resolve { let name = name.expect("didn't find a name"); // in release mode names get weird as functions can get merged // together with `mergefunc`, so only assert this in debug mode if cfg!(debug_assertions) { assert!( name.contains(expected_name), "didn't find `{}` in `{}`", expected_name, name ); } } if can_resolve { addr.expect("didn't find a symbol"); } if cfg!(debug_assertions) { let line = line.expect("didn't find a line number"); let file = file.expect("didn't find a line number"); if !expected_file.is_empty() { assert!( file.ends_with(expected_file), "{:?} didn't end with {:?}", file, expected_file ); } if expected_line != 0 { assert!( line == expected_line, "bad line number on frame for `{}`: {} != {}", expected_name, line, expected_line ); } if can_resolve_cols { let col = col.expect("didn't find a column number"); if expected_col != 0 { assert!( col == expected_col, "bad column number on frame for `{}`: {} != {}", expected_name, col, expected_col ); } } } } } #[test] fn many_threads() { let threads = (0..16) .map(|_| { thread::spawn(|| { for _ in 0..16 { backtrace::trace(|frame| { backtrace::resolve(frame.ip(), |symbol| { let _s = symbol.name().map(|s| s.to_string()); }); true }); } }) }) .collect::>(); for t in threads { t.join().unwrap() } } #[test] #[cfg(feature = "rustc-serialize")] fn is_rustc_serialize() { extern crate rustc_serialize; fn is_encode() {} fn is_decode() {} is_encode::(); is_decode::(); } #[test] #[cfg(feature = "serde")] fn is_serde() { extern crate serde; fn is_serialize() {} fn is_deserialize() {} is_serialize::(); is_deserialize::(); } #[test] fn sp_smoke_test() { let mut refs = vec![]; recursive_stack_references(&mut refs); return; #[inline(never)] fn recursive_stack_references(refs: &mut Vec) { assert!(refs.len() < 5); let x = refs.len(); refs.push(&x as *const _ as usize); if refs.len() < 5 { recursive_stack_references(refs); eprintln!("exiting: {}", x); return; } backtrace::trace(make_trace_closure(refs)); eprintln!("exiting: {}", x); } // NB: the following `make_*` functions are pulled out of line, rather than // defining their results as inline closures at their call sites, so that // the resulting closures don't have "recursive_stack_references" in their // mangled names. fn make_trace_closure<'a>( refs: &'a mut Vec, ) -> impl FnMut(&backtrace::Frame) -> bool + 'a { let mut child_sp = None; let mut child_ref = None; move |frame| { eprintln!("\n=== frame ==================================="); let mut is_recursive_stack_references = false; backtrace::resolve(frame.ip(), |sym| { is_recursive_stack_references |= (LIBBACKTRACE || GIMLI_SYMBOLIZE) && sym .name() .and_then(|name| name.as_str()) .map_or(false, |name| { eprintln!("name = {}", name); name.contains("recursive_stack_references") }) }); let sp = frame.sp() as usize; eprintln!("sp = {:p}", sp as *const u8); if sp == 0 { // If the SP is null, then we don't have an implementation for // getting the SP on this target. Just keep walking the stack, // but don't make our assertions about the on-stack pointers and // SP values. return true; } // The stack grows down. if let Some(child_sp) = child_sp { assert!(child_sp <= sp); } if is_recursive_stack_references { let r = refs.pop().unwrap(); eprintln!("ref = {:p}", r as *const u8); if sp != 0 { assert!(r > sp); if let Some(child_ref) = child_ref { assert!(sp >= child_ref); } } child_ref = Some(r); } child_sp = Some(sp); true } } }