diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /library/backtrace/tests | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/backtrace/tests')
-rw-r--r-- | library/backtrace/tests/accuracy/auxiliary.rs | 15 | ||||
-rw-r--r-- | library/backtrace/tests/accuracy/main.rs | 117 | ||||
-rw-r--r-- | library/backtrace/tests/concurrent-panics.rs | 78 | ||||
-rw-r--r-- | library/backtrace/tests/long_fn_name.rs | 48 | ||||
-rw-r--r-- | library/backtrace/tests/skip_inner_frames.rs | 44 | ||||
-rw-r--r-- | library/backtrace/tests/smoke.rs | 323 |
6 files changed, 625 insertions, 0 deletions
diff --git a/library/backtrace/tests/accuracy/auxiliary.rs b/library/backtrace/tests/accuracy/auxiliary.rs new file mode 100644 index 000000000..9c8015d9a --- /dev/null +++ b/library/backtrace/tests/accuracy/auxiliary.rs @@ -0,0 +1,15 @@ +#[inline(never)] +pub fn callback<F>(f: F) +where + F: FnOnce((&'static str, u32)), +{ + f((file!(), line!())) +} + +#[inline(always)] +pub fn callback_inlined<F>(f: F) +where + F: FnOnce((&'static str, u32)), +{ + f((file!(), line!())) +} diff --git a/library/backtrace/tests/accuracy/main.rs b/library/backtrace/tests/accuracy/main.rs new file mode 100644 index 000000000..149203a1b --- /dev/null +++ b/library/backtrace/tests/accuracy/main.rs @@ -0,0 +1,117 @@ +mod auxiliary; + +macro_rules! pos { + () => { + (file!(), line!()) + }; +} + +macro_rules! check { + ($($pos:expr),*) => ({ + verify(&[$($pos,)* pos!()]); + }) +} + +type Pos = (&'static str, u32); + +#[test] +fn doit() { + if + // Skip musl which is by default statically linked and doesn't support + // dynamic libraries. + !cfg!(target_env = "musl") + // Skip Miri, since it doesn't support dynamic libraries. + && !cfg!(miri) + { + // TODO(#238) this shouldn't have to happen first in this function, but + // currently it does. + let mut dir = std::env::current_exe().unwrap(); + dir.pop(); + if cfg!(windows) { + dir.push("dylib_dep.dll"); + } else if cfg!(target_os = "macos") { + dir.push("libdylib_dep.dylib"); + } else { + dir.push("libdylib_dep.so"); + } + unsafe { + let lib = libloading::Library::new(&dir).unwrap(); + let api = lib.get::<extern "C" fn(Pos, fn(Pos, Pos))>(b"foo").unwrap(); + api(pos!(), |a, b| { + check!(a, b); + }); + } + } + + outer(pos!()); +} + +#[inline(never)] +fn outer(main_pos: Pos) { + inner(main_pos, pos!()); + inner_inlined(main_pos, pos!()); +} + +#[inline(never)] +#[rustfmt::skip] +fn inner(main_pos: Pos, outer_pos: Pos) { + check!(main_pos, outer_pos); + check!(main_pos, outer_pos); + let inner_pos = pos!(); auxiliary::callback(|aux_pos| { + check!(main_pos, outer_pos, inner_pos, aux_pos); + }); + let inner_pos = pos!(); auxiliary::callback_inlined(|aux_pos| { + check!(main_pos, outer_pos, inner_pos, aux_pos); + }); +} + +#[inline(always)] +#[rustfmt::skip] +fn inner_inlined(main_pos: Pos, outer_pos: Pos) { + check!(main_pos, outer_pos); + check!(main_pos, outer_pos); + + #[inline(always)] + fn inner_further_inlined(main_pos: Pos, outer_pos: Pos, inner_pos: Pos) { + check!(main_pos, outer_pos, inner_pos); + } + inner_further_inlined(main_pos, outer_pos, pos!()); + + let inner_pos = pos!(); auxiliary::callback(|aux_pos| { + check!(main_pos, outer_pos, inner_pos, aux_pos); + }); + let inner_pos = pos!(); auxiliary::callback_inlined(|aux_pos| { + check!(main_pos, outer_pos, inner_pos, aux_pos); + }); + + // this tests a distinction between two independent calls to the inlined function. + // (un)fortunately, LLVM somehow merges two consecutive such calls into one node. + inner_further_inlined(main_pos, outer_pos, pos!()); +} + +fn verify(filelines: &[Pos]) { + let trace = backtrace::Backtrace::new(); + println!("-----------------------------------"); + println!("looking for:"); + for (file, line) in filelines.iter().rev() { + println!("\t{}:{}", file, line); + } + println!("found:\n{:?}", trace); + let mut symbols = trace.frames().iter().flat_map(|frame| frame.symbols()); + let mut iter = filelines.iter().rev(); + while let Some((file, line)) = iter.next() { + loop { + let sym = match symbols.next() { + Some(sym) => sym, + None => panic!("failed to find {}:{}", file, line), + }; + if let Some(filename) = sym.filename() { + if let Some(lineno) = sym.lineno() { + if filename.ends_with(file) && lineno == *line { + break; + } + } + } + } + } +} diff --git a/library/backtrace/tests/concurrent-panics.rs b/library/backtrace/tests/concurrent-panics.rs new file mode 100644 index 000000000..470245cc9 --- /dev/null +++ b/library/backtrace/tests/concurrent-panics.rs @@ -0,0 +1,78 @@ +use std::env; +use std::panic; +use std::process::Command; +use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; +use std::sync::Arc; +use std::thread; + +const PANICS: usize = 100; +const THREADS: usize = 8; +const VAR: &str = "__THE_TEST_YOU_ARE_LUKE"; + +fn main() { + // These run in docker containers on CI where they can't re-exec the test, + // so just skip these for CI. No other reason this can't run on those + // platforms though. + // Miri does not have support for re-execing a file + if cfg!(unix) + && (cfg!(target_arch = "arm") + || cfg!(target_arch = "aarch64") + || cfg!(target_arch = "s390x")) + || cfg!(miri) + { + println!("test result: ok"); + return; + } + + if env::var(VAR).is_err() { + parent(); + } else { + child(); + } +} + +fn parent() { + let me = env::current_exe().unwrap(); + let result = Command::new(&me) + .env("RUST_BACKTRACE", "1") + .env(VAR, "1") + .output() + .unwrap(); + if result.status.success() { + println!("test result: ok"); + return; + } + println!("stdout:\n{}", String::from_utf8_lossy(&result.stdout)); + println!("stderr:\n{}", String::from_utf8_lossy(&result.stderr)); + println!("code: {}", result.status); + panic!(); +} + +fn child() { + let done = Arc::new(AtomicBool::new(false)); + let done2 = done.clone(); + let a = thread::spawn(move || { + while !done2.load(SeqCst) { + format!("{:?}", backtrace::Backtrace::new()); + } + }); + + let threads = (0..THREADS) + .map(|_| { + thread::spawn(|| { + for _ in 0..PANICS { + assert!(panic::catch_unwind(|| { + panic!(); + }) + .is_err()); + } + }) + }) + .collect::<Vec<_>>(); + for thread in threads { + thread.join().unwrap(); + } + + done.store(true, SeqCst); + a.join().unwrap(); +} diff --git a/library/backtrace/tests/long_fn_name.rs b/library/backtrace/tests/long_fn_name.rs new file mode 100644 index 000000000..fa4cfda15 --- /dev/null +++ b/library/backtrace/tests/long_fn_name.rs @@ -0,0 +1,48 @@ +use backtrace::Backtrace; + +// 50-character module name +mod _234567890_234567890_234567890_234567890_234567890 { + // 50-character struct name + #[allow(non_camel_case_types)] + pub struct _234567890_234567890_234567890_234567890_234567890<T>(T); + impl<T> _234567890_234567890_234567890_234567890_234567890<T> { + #[allow(dead_code)] + pub fn new() -> crate::Backtrace { + crate::Backtrace::new() + } + } +} + +// Long function names must be truncated to (MAX_SYM_NAME - 1) characters. +// Only run this test for msvc, since gnu prints "<no info>" for all frames. +#[test] +#[cfg(all(windows, target_env = "msvc"))] +fn test_long_fn_name() { + use _234567890_234567890_234567890_234567890_234567890::_234567890_234567890_234567890_234567890_234567890 as S; + + // 10 repetitions of struct name, so fully qualified function name is + // atleast 10 * (50 + 50) * 2 = 2000 characters long. + // It's actually longer since it also includes `::`, `<>` and the + // name of the current module + let bt = S::<S<S<S<S<S<S<S<S<S<i32>>>>>>>>>>::new(); + println!("{:?}", bt); + + let mut found_long_name_frame = false; + + for frame in bt.frames() { + let symbols = frame.symbols(); + if symbols.is_empty() { + continue; + } + + if let Some(function_name) = symbols[0].name() { + let function_name = function_name.as_str().unwrap(); + if function_name.contains("::_234567890_234567890_234567890_234567890_234567890") { + found_long_name_frame = true; + assert!(function_name.len() > 200); + } + } + } + + assert!(found_long_name_frame); +} diff --git a/library/backtrace/tests/skip_inner_frames.rs b/library/backtrace/tests/skip_inner_frames.rs new file mode 100644 index 000000000..8b57bef52 --- /dev/null +++ b/library/backtrace/tests/skip_inner_frames.rs @@ -0,0 +1,44 @@ +use backtrace::Backtrace; + +// This test only works on platforms which have a working `symbol_address` +// function for frames which reports the starting address of a symbol. As a +// result it's only enabled on a few platforms. +const ENABLED: bool = cfg!(all( + // Windows hasn't really been tested, and OSX doesn't support actually + // finding an enclosing frame, so disable this + target_os = "linux", + // On ARM finding the enclosing function is simply returning the ip itself. + not(target_arch = "arm"), +)); + +#[test] +fn backtrace_new_unresolved_should_start_with_call_site_trace() { + if !ENABLED { + return; + } + let mut b = Backtrace::new_unresolved(); + b.resolve(); + println!("{:?}", b); + + assert!(!b.frames().is_empty()); + + let this_ip = backtrace_new_unresolved_should_start_with_call_site_trace as usize; + println!("this_ip: {:?}", this_ip as *const usize); + let frame_ip = b.frames().first().unwrap().symbol_address() as usize; + assert_eq!(this_ip, frame_ip); +} + +#[test] +fn backtrace_new_should_start_with_call_site_trace() { + if !ENABLED { + return; + } + let b = Backtrace::new(); + println!("{:?}", b); + + assert!(!b.frames().is_empty()); + + let this_ip = backtrace_new_should_start_with_call_site_trace as usize; + let frame_ip = b.frames().first().unwrap().symbol_address() as usize; + assert_eq!(this_ip, frame_ip); +} diff --git a/library/backtrace/tests/smoke.rs b/library/backtrace/tests/smoke.rs new file mode 100644 index 000000000..683a6f0db --- /dev/null +++ b/library/backtrace/tests/smoke.rs @@ -0,0 +1,323 @@ +use backtrace::Frame; +use std::thread; + +#[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 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()); + }); + assert!(resolved > 0); + + 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 + ); + } + + 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 + ); + } + + // dbghelp on MSVC doesn't support column numbers + if !cfg!(target_env = "msvc") { + 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::<Vec<_>>(); + + for t in threads { + t.join().unwrap() + } +} + +#[test] +#[cfg(feature = "rustc-serialize")] +fn is_rustc_serialize() { + extern crate rustc_serialize; + + fn is_encode<T: rustc_serialize::Encodable>() {} + fn is_decode<T: rustc_serialize::Decodable>() {} + + is_encode::<backtrace::Backtrace>(); + is_decode::<backtrace::Backtrace>(); +} + +#[test] +#[cfg(feature = "serde")] +fn is_serde() { + extern crate serde; + + fn is_serialize<T: serde::ser::Serialize>() {} + fn is_deserialize<T: serde::de::DeserializeOwned>() {} + + is_serialize::<backtrace::Backtrace>(); + is_deserialize::<backtrace::Backtrace>(); +} + +#[test] +fn sp_smoke_test() { + let mut refs = vec![]; + recursive_stack_references(&mut refs); + return; + + #[inline(never)] + fn recursive_stack_references(refs: &mut Vec<usize>) { + 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<usize>, + ) -> 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 |= + 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 + } + } +} |